chore: merge master and resolve CHANGELOG.md conflict

This commit is contained in:
Andoni A. committed 2026-04-15 09:04:18 +02:00
commit 3cfd4b8e0c
414 files changed
+39499 -15901

No files matched your search

+28
View File
@@ -1048,6 +1048,34 @@ class TestCheck:
)
self.verify_metadata_check_id(base_directory)
def test_vercel_checks_metadata_is_valid(self):
base_directory = os.path.abspath(
os.path.join(
os.path.dirname(__file__),
"../../../",
"prowler/providers/vercel/services",
)
)
self.verify_metadata_check_id(base_directory)
def test_vercel_checks_metadata_use_canonical_hub_urls(self):
base_directory = pathlib.Path(__file__).resolve().parents[3] / "prowler"
provider_path = base_directory / "providers" / "vercel" / "services"
invalid_urls = []
for metadata_file_path in provider_path.rglob("*.metadata.json"):
with metadata_file_path.open("r") as metadata_file:
data = json.load(metadata_file)
recommendation = data.get("Remediation", {}).get("Recommendation", {})
url = recommendation.get("Url", "")
if url.startswith("https://hub.prowler.com/checks/vercel/"):
invalid_urls.append(f"{metadata_file_path}: {url}")
assert not invalid_urls, "\n".join(invalid_urls)
def verify_metadata_check_id(self, provider_path):
errors = []
# Walk through the base directory to find all service directories
Whitespace-only changes.
@@ -0,0 +1,138 @@
from io import StringIO
from unittest import mock
from freezegun import freeze_time
from mock import patch
from prowler.lib.outputs.compliance.ccc.ccc_aws import CCC_AWS
from prowler.lib.outputs.compliance.ccc.models import CCC_AWSModel
from tests.lib.outputs.compliance.fixtures import CCC_AWS_FIXTURE
from tests.lib.outputs.fixtures.fixtures import generate_finding_output
from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER, AWS_REGION_EU_WEST_1
class TestAWSCCC:
def test_output_transform_evaluated_requirement(self):
findings = [
generate_finding_output(compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"})
]
output = CCC_AWS(findings, CCC_AWS_FIXTURE)
output_data = output.data[0]
assert isinstance(output_data, CCC_AWSModel)
assert output_data.Provider == "aws"
assert output_data.AccountId == AWS_ACCOUNT_NUMBER
assert output_data.Region == AWS_REGION_EU_WEST_1
assert output_data.Description == CCC_AWS_FIXTURE.Description
assert output_data.Requirements_Id == CCC_AWS_FIXTURE.Requirements[0].Id
assert (
output_data.Requirements_Description
== CCC_AWS_FIXTURE.Requirements[0].Description
)
attribute = CCC_AWS_FIXTURE.Requirements[0].Attributes[0]
assert output_data.Requirements_Attributes_FamilyName == attribute.FamilyName
assert (
output_data.Requirements_Attributes_FamilyDescription
== attribute.FamilyDescription
)
assert output_data.Requirements_Attributes_Section == attribute.Section
assert output_data.Requirements_Attributes_SubSection == attribute.SubSection
assert (
output_data.Requirements_Attributes_SubSectionObjective
== attribute.SubSectionObjective
)
assert (
output_data.Requirements_Attributes_Applicability == attribute.Applicability
)
assert (
output_data.Requirements_Attributes_Recommendation
== attribute.Recommendation
)
assert (
output_data.Requirements_Attributes_SectionThreatMappings
== attribute.SectionThreatMappings
)
assert (
output_data.Requirements_Attributes_SectionGuidelineMappings
== attribute.SectionGuidelineMappings
)
assert output_data.Status == "PASS"
assert output_data.StatusExtended == ""
assert output_data.ResourceId == ""
assert output_data.ResourceName == ""
assert output_data.CheckId == "service_test_check_id"
assert output_data.Muted is False
def test_output_transform_manual_requirement(self):
# Use a finding for the evaluated requirement so the manual one is appended
# by the manual-loop branch (Checks=[]).
findings = [
generate_finding_output(compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"})
]
output = CCC_AWS(findings, CCC_AWS_FIXTURE)
# data[0] is the evaluated PASS row, data[1] is the manual row
manual_row = output.data[1]
assert isinstance(manual_row, CCC_AWSModel)
assert manual_row.Provider == "aws"
assert manual_row.AccountId == ""
assert manual_row.Region == ""
assert manual_row.Description == CCC_AWS_FIXTURE.Description
assert manual_row.Requirements_Id == CCC_AWS_FIXTURE.Requirements[1].Id
manual_attribute = CCC_AWS_FIXTURE.Requirements[1].Attributes[0]
assert (
manual_row.Requirements_Attributes_FamilyName == manual_attribute.FamilyName
)
assert manual_row.Requirements_Attributes_Section == manual_attribute.Section
assert manual_row.Status == "MANUAL"
assert manual_row.StatusExtended == "Manual check"
assert manual_row.ResourceId == "manual_check"
assert manual_row.ResourceName == "Manual check"
assert manual_row.CheckId == "manual"
assert manual_row.Muted is False
@freeze_time("2025-01-01 00:00:00")
@mock.patch(
"prowler.lib.outputs.compliance.ccc.ccc_aws.timestamp",
"2025-01-01 00:00:00",
)
def test_batch_write_data_to_file(self):
mock_file = StringIO()
findings = [
generate_finding_output(compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"})
]
output = CCC_AWS(findings, CCC_AWS_FIXTURE)
output._file_descriptor = mock_file
with patch.object(mock_file, "close", return_value=None):
output.batch_write_data_to_file()
mock_file.seek(0)
content = mock_file.read()
# Header check: AWS-specific columns must be present
header = content.split("\r\n", 1)[0]
assert "ACCOUNTID" in header
assert "REGION" in header
assert "REQUIREMENTS_ATTRIBUTES_FAMILYNAME" in header
assert "REQUIREMENTS_ATTRIBUTES_SECTION" in header
assert "REQUIREMENTS_ATTRIBUTES_APPLICABILITY" in header
assert "REQUIREMENTS_ATTRIBUTES_SECTIONTHREATMAPPINGS" in header
# Header should NOT contain Azure or GCP-only columns
assert "SUBSCRIPTIONID" not in header
assert "PROJECTID" not in header
# Body checks: evaluated row + manual row
rows = [r for r in content.split("\r\n") if r]
assert len(rows) == 3 # header + evaluated + manual
assert "CCC.Core.CN01.AR01" in rows[1]
assert "PASS" in rows[1]
assert AWS_ACCOUNT_NUMBER in rows[1]
assert AWS_REGION_EU_WEST_1 in rows[1]
assert "CCC.IAM.CN01.AR01" in rows[2]
assert "MANUAL" in rows[2]
assert "manual_check" in rows[2]
# The frozen timestamp should appear
assert "2025-01-01 00:00:00" in rows[1]
@@ -0,0 +1,99 @@
from io import StringIO
from unittest import mock
from freezegun import freeze_time
from mock import patch
from prowler.lib.outputs.compliance.ccc.ccc_azure import CCC_Azure
from prowler.lib.outputs.compliance.ccc.models import CCC_AzureModel
from tests.lib.outputs.compliance.fixtures import CCC_AZURE_FIXTURE
from tests.lib.outputs.fixtures.fixtures import generate_finding_output
from tests.providers.azure.azure_fixtures import AZURE_SUBSCRIPTION_ID
AZURE_LOCATION = "westeurope"
class TestAzureCCC:
def test_output_transform_evaluated_requirement(self):
findings = [
generate_finding_output(
provider="azure",
compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"},
account_uid=AZURE_SUBSCRIPTION_ID,
region=AZURE_LOCATION,
)
]
output = CCC_Azure(findings, CCC_AZURE_FIXTURE)
output_data = output.data[0]
assert isinstance(output_data, CCC_AzureModel)
assert output_data.Provider == "azure"
assert output_data.SubscriptionId == AZURE_SUBSCRIPTION_ID
assert output_data.Location == AZURE_LOCATION
assert output_data.Description == CCC_AZURE_FIXTURE.Description
assert output_data.Requirements_Id == CCC_AZURE_FIXTURE.Requirements[0].Id
attribute = CCC_AZURE_FIXTURE.Requirements[0].Attributes[0]
assert output_data.Requirements_Attributes_FamilyName == attribute.FamilyName
assert output_data.Requirements_Attributes_Section == attribute.Section
assert (
output_data.Requirements_Attributes_Applicability == attribute.Applicability
)
assert output_data.Status == "PASS"
assert output_data.CheckId == "service_test_check_id"
def test_output_transform_manual_requirement(self):
findings = [
generate_finding_output(
provider="azure",
compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"},
account_uid=AZURE_SUBSCRIPTION_ID,
region=AZURE_LOCATION,
)
]
output = CCC_Azure(findings, CCC_AZURE_FIXTURE)
manual_row = output.data[1]
assert isinstance(manual_row, CCC_AzureModel)
assert manual_row.Provider == "azure"
assert manual_row.SubscriptionId == ""
assert manual_row.Location == ""
assert manual_row.Requirements_Id == CCC_AZURE_FIXTURE.Requirements[1].Id
assert manual_row.Status == "MANUAL"
assert manual_row.CheckId == "manual"
@freeze_time("2025-01-01 00:00:00")
@mock.patch(
"prowler.lib.outputs.compliance.ccc.ccc_azure.timestamp",
"2025-01-01 00:00:00",
)
def test_batch_write_data_to_file(self):
mock_file = StringIO()
findings = [
generate_finding_output(
provider="azure",
compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"},
account_uid=AZURE_SUBSCRIPTION_ID,
region=AZURE_LOCATION,
)
]
output = CCC_Azure(findings, CCC_AZURE_FIXTURE)
output._file_descriptor = mock_file
with patch.object(mock_file, "close", return_value=None):
output.batch_write_data_to_file()
mock_file.seek(0)
content = mock_file.read()
header = content.split("\r\n", 1)[0]
assert "SUBSCRIPTIONID" in header
assert "LOCATION" in header
assert "ACCOUNTID" not in header
assert "PROJECTID" not in header
assert "REGION" not in header
rows = [r for r in content.split("\r\n") if r]
assert len(rows) == 3
assert "CCC.Core.CN01.AR01" in rows[1]
assert AZURE_SUBSCRIPTION_ID in rows[1]
assert "CCC.IAM.CN01.AR01" in rows[2]
assert "MANUAL" in rows[2]
@@ -0,0 +1,99 @@
from io import StringIO
from unittest import mock
from freezegun import freeze_time
from mock import patch
from prowler.lib.outputs.compliance.ccc.ccc_gcp import CCC_GCP
from prowler.lib.outputs.compliance.ccc.models import CCC_GCPModel
from tests.lib.outputs.compliance.fixtures import CCC_GCP_FIXTURE
from tests.lib.outputs.fixtures.fixtures import generate_finding_output
GCP_PROJECT_ID = "test-project"
GCP_LOCATION = "europe-west1"
class TestGCPCCC:
def test_output_transform_evaluated_requirement(self):
findings = [
generate_finding_output(
provider="gcp",
compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"},
account_uid=GCP_PROJECT_ID,
region=GCP_LOCATION,
)
]
output = CCC_GCP(findings, CCC_GCP_FIXTURE)
output_data = output.data[0]
assert isinstance(output_data, CCC_GCPModel)
assert output_data.Provider == "gcp"
assert output_data.ProjectId == GCP_PROJECT_ID
assert output_data.Location == GCP_LOCATION
assert output_data.Description == CCC_GCP_FIXTURE.Description
assert output_data.Requirements_Id == CCC_GCP_FIXTURE.Requirements[0].Id
attribute = CCC_GCP_FIXTURE.Requirements[0].Attributes[0]
assert output_data.Requirements_Attributes_FamilyName == attribute.FamilyName
assert output_data.Requirements_Attributes_Section == attribute.Section
assert (
output_data.Requirements_Attributes_Applicability == attribute.Applicability
)
assert output_data.Status == "PASS"
assert output_data.CheckId == "service_test_check_id"
def test_output_transform_manual_requirement(self):
findings = [
generate_finding_output(
provider="gcp",
compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"},
account_uid=GCP_PROJECT_ID,
region=GCP_LOCATION,
)
]
output = CCC_GCP(findings, CCC_GCP_FIXTURE)
manual_row = output.data[1]
assert isinstance(manual_row, CCC_GCPModel)
assert manual_row.Provider == "gcp"
assert manual_row.ProjectId == ""
assert manual_row.Location == ""
assert manual_row.Requirements_Id == CCC_GCP_FIXTURE.Requirements[1].Id
assert manual_row.Status == "MANUAL"
assert manual_row.CheckId == "manual"
@freeze_time("2025-01-01 00:00:00")
@mock.patch(
"prowler.lib.outputs.compliance.ccc.ccc_gcp.timestamp",
"2025-01-01 00:00:00",
)
def test_batch_write_data_to_file(self):
mock_file = StringIO()
findings = [
generate_finding_output(
provider="gcp",
compliance={"CCC-v2025.10": "CCC.Core.CN01.AR01"},
account_uid=GCP_PROJECT_ID,
region=GCP_LOCATION,
)
]
output = CCC_GCP(findings, CCC_GCP_FIXTURE)
output._file_descriptor = mock_file
with patch.object(mock_file, "close", return_value=None):
output.batch_write_data_to_file()
mock_file.seek(0)
content = mock_file.read()
header = content.split("\r\n", 1)[0]
assert "PROJECTID" in header
assert "LOCATION" in header
assert "ACCOUNTID" not in header
assert "SUBSCRIPTIONID" not in header
assert "REGION" not in header
rows = [r for r in content.split("\r\n") if r]
assert len(rows) == 3
assert "CCC.Core.CN01.AR01" in rows[1]
assert GCP_PROJECT_ID in rows[1]
assert "CCC.IAM.CN01.AR01" in rows[2]
assert "MANUAL" in rows[2]
+167
View File
@@ -1,5 +1,6 @@
from prowler.lib.check.compliance_models import (
AWS_Well_Architected_Requirement_Attribute,
CCC_Requirement_Attribute,
CIS_Requirement_Attribute,
Compliance,
Compliance_Requirement,
@@ -1022,3 +1023,169 @@ PROWLER_THREATSCORE_M365 = Compliance(
),
],
)
# CCC fixtures cover the three providers Prowler ships catalogs for. Each
# fixture has one auto-evaluated requirement (with Checks) and one manual
# requirement (Checks=[]) so test suites can exercise both paths.
CCC_AWS_FIXTURE = Compliance(
Framework="CCC",
Name="Common Cloud Controls Catalog (CCC)",
Provider="AWS",
Version="v2025.10",
Description="Common Cloud Controls Catalog (CCC) for AWS",
Requirements=[
Compliance_Requirement(
Checks=["service_test_check_id"],
Id="CCC.Core.CN01.AR01",
Description="When a port is exposed for non-SSH network traffic, all traffic MUST include a TLS handshake AND be encrypted using TLS 1.3 or higher.",
Attributes=[
CCC_Requirement_Attribute(
FamilyName="Data",
FamilyDescription="The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.",
Section="CCC.Core.CN01 Encrypt Data for Transmission",
SubSection="",
SubSectionObjective="Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.",
Applicability=["tlp-green", "tlp-amber", "tlp-red"],
Recommendation="Most cloud services enable TLS 1.3 by default.",
SectionThreatMappings=[
{"ReferenceId": "CCC", "Identifiers": ["CCC.Core.TH02"]}
],
SectionGuidelineMappings=[
{"ReferenceId": "CCM", "Identifiers": ["CEK-03", "CEK-04"]}
],
)
],
),
Compliance_Requirement(
Checks=[],
Id="CCC.IAM.CN01.AR01",
Description="When an identity policy for a non-administrative principal is evaluated, it MUST NOT grant permissions for creating credentials or generating temporary session tokens.",
Attributes=[
CCC_Requirement_Attribute(
FamilyName="Identity and Access Management",
FamilyDescription="Controls that restrict who can access and modify IAM resources.",
Section="CCC.IAM.CN01 Restrict IAM User Credentials Creation",
SubSection="",
SubSectionObjective="Prevent non-administrative principals from creating new long-lived credentials.",
Applicability=["tlp-clear", "tlp-green", "tlp-amber", "tlp-red"],
Recommendation="",
SectionThreatMappings=[
{"ReferenceId": "CCC", "Identifiers": ["CCC.IAM.TH03"]}
],
SectionGuidelineMappings=[
{"ReferenceId": "NIST-CSF", "Identifiers": ["PR.AA-05"]}
],
)
],
),
],
)
CCC_AZURE_FIXTURE = Compliance(
Framework="CCC",
Name="Common Cloud Controls Catalog (CCC)",
Provider="Azure",
Version="v2025.10",
Description="Common Cloud Controls Catalog (CCC) for Azure",
Requirements=[
Compliance_Requirement(
Checks=["service_test_check_id"],
Id="CCC.Core.CN01.AR01",
Description="When a port is exposed for non-SSH network traffic, all traffic MUST include a TLS handshake AND be encrypted using TLS 1.3 or higher.",
Attributes=[
CCC_Requirement_Attribute(
FamilyName="Data",
FamilyDescription="The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.",
Section="CCC.Core.CN01 Encrypt Data for Transmission",
SubSection="",
SubSectionObjective="Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.",
Applicability=["tlp-green", "tlp-amber", "tlp-red"],
Recommendation="Most cloud services enable TLS 1.3 by default.",
SectionThreatMappings=[
{"ReferenceId": "CCC", "Identifiers": ["CCC.Core.TH02"]}
],
SectionGuidelineMappings=[
{"ReferenceId": "CCM", "Identifiers": ["CEK-03", "CEK-04"]}
],
)
],
),
Compliance_Requirement(
Checks=[],
Id="CCC.IAM.CN01.AR01",
Description="When an identity policy for a non-administrative principal is evaluated, it MUST NOT grant permissions for creating credentials.",
Attributes=[
CCC_Requirement_Attribute(
FamilyName="Identity and Access Management",
FamilyDescription="Controls that restrict who can access and modify IAM resources.",
Section="CCC.IAM.CN01 Restrict IAM User Credentials Creation",
SubSection="",
SubSectionObjective="Prevent non-administrative principals from creating new long-lived credentials.",
Applicability=["tlp-clear", "tlp-green", "tlp-amber", "tlp-red"],
Recommendation="",
SectionThreatMappings=[
{"ReferenceId": "CCC", "Identifiers": ["CCC.IAM.TH03"]}
],
SectionGuidelineMappings=[
{"ReferenceId": "NIST-CSF", "Identifiers": ["PR.AA-05"]}
],
)
],
),
],
)
CCC_GCP_FIXTURE = Compliance(
Framework="CCC",
Name="Common Cloud Controls Catalog (CCC)",
Provider="GCP",
Version="v2025.10",
Description="Common Cloud Controls Catalog (CCC) for GCP",
Requirements=[
Compliance_Requirement(
Checks=["service_test_check_id"],
Id="CCC.Core.CN01.AR01",
Description="When a port is exposed for non-SSH network traffic, all traffic MUST include a TLS handshake AND be encrypted using TLS 1.3 or higher.",
Attributes=[
CCC_Requirement_Attribute(
FamilyName="Data",
FamilyDescription="The Data control family ensures the confidentiality, integrity, availability, and sovereignty of data across its lifecycle.",
Section="CCC.Core.CN01 Encrypt Data for Transmission",
SubSection="",
SubSectionObjective="Ensure that all communications are encrypted in transit to protect data integrity and confidentiality.",
Applicability=["tlp-green", "tlp-amber", "tlp-red"],
Recommendation="Most cloud services enable TLS 1.3 by default.",
SectionThreatMappings=[
{"ReferenceId": "CCC", "Identifiers": ["CCC.Core.TH02"]}
],
SectionGuidelineMappings=[
{"ReferenceId": "CCM", "Identifiers": ["CEK-03", "CEK-04"]}
],
)
],
),
Compliance_Requirement(
Checks=[],
Id="CCC.IAM.CN01.AR01",
Description="When an identity policy for a non-administrative principal is evaluated, it MUST NOT grant permissions for creating credentials.",
Attributes=[
CCC_Requirement_Attribute(
FamilyName="Identity and Access Management",
FamilyDescription="Controls that restrict who can access and modify IAM resources.",
Section="CCC.IAM.CN01 Restrict IAM User Credentials Creation",
SubSection="",
SubSectionObjective="Prevent non-administrative principals from creating new long-lived credentials.",
Applicability=["tlp-clear", "tlp-green", "tlp-amber", "tlp-red"],
Recommendation="",
SectionThreatMappings=[
{"ReferenceId": "CCC", "Identifiers": ["CCC.IAM.TH03"]}
],
SectionGuidelineMappings=[
{"ReferenceId": "NIST-CSF", "Identifiers": ["PR.AA-05"]}
],
)
],
),
],
)
@@ -0,0 +1,357 @@
from json import dumps
from unittest import mock
from boto3 import client
from moto import mock_aws
from prowler.providers.aws.services.iam.iam_service import Role
from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider
AWS_ACCOUNT_ID = "123456789012"
ASSUME_ROLE_POLICY_DOCUMENT = {
"Version": "2012-10-17",
"Statement": {
"Sid": "test",
"Effect": "Allow",
"Principal": {"AWS": f"arn:aws:iam::{AWS_ACCOUNT_ID}:root"},
"Action": "sts:AssumeRole",
},
}
class Test_bedrock_full_access_policy_attached:
@mock_aws(config={"iam": {"load_aws_managed_policies": True}})
def test_no_roles(self):
from prowler.providers.aws.services.iam.iam_service import IAM
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached.iam_client",
new=IAM(aws_provider),
),
):
from prowler.providers.aws.services.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached import (
bedrock_full_access_policy_attached,
)
check = bedrock_full_access_policy_attached()
result = check.execute()
assert len(result) == 0
@mock_aws(config={"iam": {"load_aws_managed_policies": True}})
def test_role_without_bedrock_full_access_policy(self):
iam = client("iam")
role_name = "test"
response = iam.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=dumps(ASSUME_ROLE_POLICY_DOCUMENT),
)
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
from prowler.providers.aws.services.iam.iam_service import IAM
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached.iam_client",
new=IAM(aws_provider),
),
):
from prowler.providers.aws.services.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached import (
bedrock_full_access_policy_attached,
)
check = bedrock_full_access_policy_attached()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "IAM Role test does not have AmazonBedrockFullAccess policy attached."
)
assert result[0].resource_id == "test"
assert result[0].resource_arn == response["Role"]["Arn"]
assert result[0].resource_tags == []
assert result[0].region == AWS_REGION_US_EAST_1
@mock_aws(config={"iam": {"load_aws_managed_policies": True}})
def test_role_with_other_policy(self):
iam = client("iam")
role_name = "test"
response = iam.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=dumps(ASSUME_ROLE_POLICY_DOCUMENT),
)
iam.attach_role_policy(
RoleName=role_name,
PolicyArn="arn:aws:iam::aws:policy/SecurityAudit",
)
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
from prowler.providers.aws.services.iam.iam_service import IAM
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached.iam_client",
new=IAM(aws_provider),
),
):
from prowler.providers.aws.services.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached import (
bedrock_full_access_policy_attached,
)
check = bedrock_full_access_policy_attached()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "IAM Role test does not have AmazonBedrockFullAccess policy attached."
)
assert result[0].resource_id == "test"
assert result[0].resource_arn == response["Role"]["Arn"]
assert result[0].resource_tags == []
assert result[0].region == AWS_REGION_US_EAST_1
@mock_aws(config={"iam": {"load_aws_managed_policies": True}})
def test_role_with_bedrock_full_access_policy(self):
iam = client("iam")
role_name = "test"
response = iam.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=dumps(ASSUME_ROLE_POLICY_DOCUMENT),
)
iam.attach_role_policy(
RoleName=role_name,
PolicyArn="arn:aws:iam::aws:policy/AmazonBedrockFullAccess",
)
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
from prowler.providers.aws.services.iam.iam_service import IAM
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached.iam_client",
new=IAM(aws_provider),
),
):
from prowler.providers.aws.services.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached import (
bedrock_full_access_policy_attached,
)
check = bedrock_full_access_policy_attached()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "IAM Role test has AmazonBedrockFullAccess policy attached."
)
assert result[0].resource_id == "test"
assert result[0].resource_arn == response["Role"]["Arn"]
assert result[0].resource_tags == []
assert result[0].region == AWS_REGION_US_EAST_1
@mock_aws(config={"iam": {"load_aws_managed_policies": True}})
def test_asterisk_principal_role_with_bedrock_full_access_policy(self):
iam = client("iam")
role_name = "test"
assume_role_policy_document = {
"Version": "2012-10-17",
"Statement": {
"Sid": "test",
"Effect": "Allow",
"Principal": {"AWS": "*"},
"Action": "sts:AssumeRole",
},
}
response = iam.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=dumps(assume_role_policy_document),
)
iam.attach_role_policy(
RoleName=role_name,
PolicyArn="arn:aws:iam::aws:policy/AmazonBedrockFullAccess",
)
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
from prowler.providers.aws.services.iam.iam_service import IAM
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached.iam_client",
new=IAM(aws_provider),
),
):
from prowler.providers.aws.services.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached import (
bedrock_full_access_policy_attached,
)
check = bedrock_full_access_policy_attached()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "IAM Role test has AmazonBedrockFullAccess policy attached."
)
assert result[0].resource_id == "test"
assert result[0].resource_arn == response["Role"]["Arn"]
assert result[0].resource_tags == []
assert result[0].region == AWS_REGION_US_EAST_1
@mock_aws(config={"iam": {"load_aws_managed_policies": True}})
def test_multiple_roles_mixed_policies(self):
iam = client("iam")
# Create a compliant role (no AmazonBedrockFullAccess)
compliant_response = iam.create_role(
RoleName="compliant-role",
AssumeRolePolicyDocument=dumps(ASSUME_ROLE_POLICY_DOCUMENT),
)
iam.attach_role_policy(
RoleName="compliant-role",
PolicyArn="arn:aws:iam::aws:policy/SecurityAudit",
)
# Create a non-compliant role (with AmazonBedrockFullAccess)
non_compliant_response = iam.create_role(
RoleName="non-compliant-role",
AssumeRolePolicyDocument=dumps(ASSUME_ROLE_POLICY_DOCUMENT),
)
iam.attach_role_policy(
RoleName="non-compliant-role",
PolicyArn="arn:aws:iam::aws:policy/AmazonBedrockFullAccess",
)
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
from prowler.providers.aws.services.iam.iam_service import IAM
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached.iam_client",
new=IAM(aws_provider),
),
):
from prowler.providers.aws.services.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached import (
bedrock_full_access_policy_attached,
)
check = bedrock_full_access_policy_attached()
result = check.execute()
assert len(result) == 2
# Sort results by resource_id for deterministic assertions
result = sorted(result, key=lambda r: r.resource_id)
# Compliant role
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "IAM Role compliant-role does not have AmazonBedrockFullAccess policy attached."
)
assert result[0].resource_id == "compliant-role"
assert result[0].resource_arn == compliant_response["Role"]["Arn"]
assert result[0].region == AWS_REGION_US_EAST_1
# Non-compliant role
assert result[1].status == "FAIL"
assert (
result[1].status_extended
== "IAM Role non-compliant-role has AmazonBedrockFullAccess policy attached."
)
assert result[1].resource_id == "non-compliant-role"
assert result[1].resource_arn == non_compliant_response["Role"]["Arn"]
assert result[1].region == AWS_REGION_US_EAST_1
@mock_aws(config={"iam": {"load_aws_managed_policies": True}})
def test_only_aws_service_linked_roles(self):
iam_client = mock.MagicMock
iam_client.roles = []
iam_client.roles.append(
Role(
name="AWSServiceRoleForAmazonGuardDuty",
arn="arn:aws:iam::106908755756:role/aws-service-role/guardduty.amazonaws.com/AWSServiceRoleForAmazonGuardDuty",
assume_role_policy={
"Version": "2008-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"Service": "ec2.amazonaws.com"},
"Action": "sts:AssumeRole",
}
],
},
is_service_role=True,
)
)
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached.iam_client",
new=iam_client,
),
):
from prowler.providers.aws.services.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached import (
bedrock_full_access_policy_attached,
)
check = bedrock_full_access_policy_attached()
result = check.execute()
assert len(result) == 0
@mock_aws(config={"iam": {"load_aws_managed_policies": True}})
def test_access_denied(self):
iam_client = mock.MagicMock
iam_client.roles = None
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached.iam_client",
new=iam_client,
),
):
from prowler.providers.aws.services.bedrock.bedrock_full_access_policy_attached.bedrock_full_access_policy_attached import (
bedrock_full_access_policy_attached,
)
check = bedrock_full_access_policy_attached()
result = check.execute()
assert len(result) == 0
@@ -0,0 +1,558 @@
from unittest import mock
from boto3 import client
from moto import mock_aws
from prowler.providers.aws.services.bedrock.bedrock_service import (
Guardrail,
LoggingConfiguration,
)
from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider
BEDROCK_SERVICES = [
"com.amazonaws.us-east-1.bedrock",
"com.amazonaws.us-east-1.bedrock-runtime",
"com.amazonaws.us-east-1.bedrock-agent",
"com.amazonaws.us-east-1.bedrock-agent-runtime",
"com.amazonaws.us-east-1.bedrock-mantle",
]
MOCK_BEDROCK_CLIENT = mock.MagicMock(
logging_configurations={AWS_REGION_US_EAST_1: LoggingConfiguration(enabled=True)},
guardrails={},
)
MOCK_BEDROCK_AGENT_CLIENT = mock.MagicMock(agents={})
MOCK_BEDROCK_CLIENT_NO_ACTIVITY = mock.MagicMock(
logging_configurations={AWS_REGION_US_EAST_1: LoggingConfiguration(enabled=False)},
guardrails={},
)
MOCK_BEDROCK_AGENT_CLIENT_NO_ACTIVITY = mock.MagicMock(agents={})
CHECK_MODULE = "prowler.providers.aws.services.bedrock.bedrock_vpc_endpoints_configured.bedrock_vpc_endpoints_configured"
class Test_bedrock_vpc_endpoints_configured:
@mock_aws
def test_no_resources(self):
"""Test with no in-use VPCs and scan_unused_services disabled - should return no results."""
client("ec2", region_name=AWS_REGION_US_EAST_1)
from prowler.providers.aws.services.vpc.vpc_service import VPC
aws_provider = set_mocked_aws_provider(
[AWS_REGION_US_EAST_1], scan_unused_services=False
)
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with (
mock.patch(
f"{CHECK_MODULE}.vpc_client",
new=VPC(aws_provider),
),
mock.patch(
f"{CHECK_MODULE}.bedrock_client",
new=MOCK_BEDROCK_CLIENT,
),
mock.patch(
f"{CHECK_MODULE}.bedrock_agent_client",
new=MOCK_BEDROCK_AGENT_CLIENT,
),
):
from prowler.providers.aws.services.bedrock.bedrock_vpc_endpoints_configured.bedrock_vpc_endpoints_configured import (
bedrock_vpc_endpoints_configured,
)
check = bedrock_vpc_endpoints_configured()
result = check.execute()
assert len(result) == 0
@mock_aws
def test_no_bedrock_activity(self):
"""Test VPCs in region with no Bedrock activity - should return no results."""
ec2_client = client("ec2", region_name=AWS_REGION_US_EAST_1)
ec2_client.describe_vpcs()
from prowler.providers.aws.services.vpc.vpc_service import VPC
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with (
mock.patch(
f"{CHECK_MODULE}.vpc_client",
new=VPC(aws_provider),
),
mock.patch(
f"{CHECK_MODULE}.bedrock_client",
new=MOCK_BEDROCK_CLIENT_NO_ACTIVITY,
),
mock.patch(
f"{CHECK_MODULE}.bedrock_agent_client",
new=MOCK_BEDROCK_AGENT_CLIENT_NO_ACTIVITY,
),
):
from prowler.providers.aws.services.bedrock.bedrock_vpc_endpoints_configured.bedrock_vpc_endpoints_configured import (
bedrock_vpc_endpoints_configured,
)
check = bedrock_vpc_endpoints_configured()
result = check.execute()
assert len(result) == 0
@mock_aws
def test_vpc_no_endpoints(self):
"""Test VPC with no VPC endpoints at all - should FAIL with all services missing."""
ec2_client = client("ec2", region_name=AWS_REGION_US_EAST_1)
vpc_id = ec2_client.describe_vpcs()["Vpcs"][0]["VpcId"]
from prowler.providers.aws.services.vpc.vpc_service import VPC
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with (
mock.patch(
f"{CHECK_MODULE}.vpc_client",
new=VPC(aws_provider),
),
mock.patch(
f"{CHECK_MODULE}.bedrock_client",
new=MOCK_BEDROCK_CLIENT,
),
mock.patch(
f"{CHECK_MODULE}.bedrock_agent_client",
new=MOCK_BEDROCK_AGENT_CLIENT,
),
):
from prowler.providers.aws.services.bedrock.bedrock_vpc_endpoints_configured.bedrock_vpc_endpoints_configured import (
bedrock_vpc_endpoints_configured,
)
check = bedrock_vpc_endpoints_configured()
result = check.execute()
assert len(result) == 1
assert result[0].resource_id == vpc_id
assert result[0].region == AWS_REGION_US_EAST_1
assert result[0].status == "FAIL"
assert "Bedrock control plane" in result[0].status_extended
assert "Bedrock runtime" in result[0].status_extended
assert "Bedrock agent control plane" in result[0].status_extended
assert "Bedrock agent runtime" in result[0].status_extended
assert "Bedrock Mantle" in result[0].status_extended
@mock_aws
def test_vpc_only_bedrock_runtime_endpoint(self):
"""Test VPC with only Bedrock runtime endpoint - should FAIL with three services missing."""
ec2_client = client("ec2", region_name=AWS_REGION_US_EAST_1)
vpc = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]
route_table = ec2_client.create_route_table(VpcId=vpc["VpcId"])["RouteTable"]
ec2_client.create_vpc_endpoint(
VpcId=vpc["VpcId"],
ServiceName="com.amazonaws.us-east-1.bedrock-runtime",
RouteTableIds=[route_table["RouteTableId"]],
VpcEndpointType="Interface",
)
from prowler.providers.aws.services.vpc.vpc_service import VPC
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with (
mock.patch(
f"{CHECK_MODULE}.vpc_client",
new=VPC(aws_provider),
),
mock.patch(
f"{CHECK_MODULE}.bedrock_client",
new=MOCK_BEDROCK_CLIENT,
),
mock.patch(
f"{CHECK_MODULE}.bedrock_agent_client",
new=MOCK_BEDROCK_AGENT_CLIENT,
),
):
from prowler.providers.aws.services.bedrock.bedrock_vpc_endpoints_configured.bedrock_vpc_endpoints_configured import (
bedrock_vpc_endpoints_configured,
)
check = bedrock_vpc_endpoints_configured()
result = check.execute()
# Default VPC + created VPC
assert len(result) == 2
finding = next(f for f in result if f.resource_id == vpc["VpcId"])
assert finding.region == AWS_REGION_US_EAST_1
assert finding.status == "FAIL"
assert "Bedrock runtime" not in finding.status_extended
assert "Bedrock control plane" in finding.status_extended
assert "Bedrock agent control plane" in finding.status_extended
assert "Bedrock agent runtime" in finding.status_extended
assert "Bedrock Mantle" in finding.status_extended
assert (
finding.resource_arn
== f"arn:aws:ec2:{AWS_REGION_US_EAST_1}:123456789012:vpc/{vpc['VpcId']}"
)
@mock_aws
def test_vpc_only_bedrock_agent_runtime_endpoint(self):
"""Test VPC with only Bedrock agent runtime endpoint - should FAIL with three services missing."""
ec2_client = client("ec2", region_name=AWS_REGION_US_EAST_1)
vpc = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]
route_table = ec2_client.create_route_table(VpcId=vpc["VpcId"])["RouteTable"]
ec2_client.create_vpc_endpoint(
VpcId=vpc["VpcId"],
ServiceName="com.amazonaws.us-east-1.bedrock-agent-runtime",
RouteTableIds=[route_table["RouteTableId"]],
VpcEndpointType="Interface",
)
from prowler.providers.aws.services.vpc.vpc_service import VPC
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with (
mock.patch(
f"{CHECK_MODULE}.vpc_client",
new=VPC(aws_provider),
),
mock.patch(
f"{CHECK_MODULE}.bedrock_client",
new=MOCK_BEDROCK_CLIENT,
),
mock.patch(
f"{CHECK_MODULE}.bedrock_agent_client",
new=MOCK_BEDROCK_AGENT_CLIENT,
),
):
from prowler.providers.aws.services.bedrock.bedrock_vpc_endpoints_configured.bedrock_vpc_endpoints_configured import (
bedrock_vpc_endpoints_configured,
)
check = bedrock_vpc_endpoints_configured()
result = check.execute()
assert len(result) == 2
finding = next(f for f in result if f.resource_id == vpc["VpcId"])
assert finding.region == AWS_REGION_US_EAST_1
assert finding.status == "FAIL"
assert "Bedrock agent runtime" not in finding.status_extended
assert "Bedrock control plane" in finding.status_extended
assert "Bedrock runtime" in finding.status_extended
assert "Bedrock agent control plane" in finding.status_extended
assert "Bedrock Mantle" in finding.status_extended
assert (
finding.resource_arn
== f"arn:aws:ec2:{AWS_REGION_US_EAST_1}:123456789012:vpc/{vpc['VpcId']}"
)
@mock_aws
def test_vpc_all_bedrock_endpoints(self):
"""Test VPC with all four Bedrock endpoints - should PASS."""
ec2_client = client("ec2", region_name=AWS_REGION_US_EAST_1)
vpc = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]
vpc_default_id = ec2_client.describe_vpcs()["Vpcs"][0]["VpcId"]
route_table = ec2_client.create_route_table(VpcId=vpc["VpcId"])["RouteTable"]
for svc in BEDROCK_SERVICES:
ec2_client.create_vpc_endpoint(
VpcId=vpc["VpcId"],
ServiceName=svc,
RouteTableIds=[route_table["RouteTableId"]],
VpcEndpointType="Interface",
)
from prowler.providers.aws.services.vpc.vpc_service import VPC
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with (
mock.patch(
f"{CHECK_MODULE}.vpc_client",
new=VPC(aws_provider),
),
mock.patch(
f"{CHECK_MODULE}.bedrock_client",
new=MOCK_BEDROCK_CLIENT,
),
mock.patch(
f"{CHECK_MODULE}.bedrock_agent_client",
new=MOCK_BEDROCK_AGENT_CLIENT,
),
):
from prowler.providers.aws.services.bedrock.bedrock_vpc_endpoints_configured.bedrock_vpc_endpoints_configured import (
bedrock_vpc_endpoints_configured,
)
check = bedrock_vpc_endpoints_configured()
result = check.execute()
assert len(result) == 2
finding = next(f for f in result if f.resource_id == vpc["VpcId"])
assert finding.region == AWS_REGION_US_EAST_1
assert finding.status == "PASS"
assert (
finding.status_extended
== f"VPC {vpc['VpcId']} has VPC endpoints for all Bedrock services."
)
assert (
finding.resource_arn
== f"arn:aws:ec2:{AWS_REGION_US_EAST_1}:123456789012:vpc/{vpc['VpcId']}"
)
# Default VPC should FAIL
default_finding = next(
f for f in result if f.resource_id == vpc_default_id
)
assert default_finding.status == "FAIL"
@mock_aws
def test_vpc_only_runtime_endpoints(self):
"""Test VPC with only both runtime endpoints but missing control plane endpoints - should FAIL."""
ec2_client = client("ec2", region_name=AWS_REGION_US_EAST_1)
vpc = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]
route_table = ec2_client.create_route_table(VpcId=vpc["VpcId"])["RouteTable"]
ec2_client.create_vpc_endpoint(
VpcId=vpc["VpcId"],
ServiceName="com.amazonaws.us-east-1.bedrock-runtime",
RouteTableIds=[route_table["RouteTableId"]],
VpcEndpointType="Interface",
)
ec2_client.create_vpc_endpoint(
VpcId=vpc["VpcId"],
ServiceName="com.amazonaws.us-east-1.bedrock-agent-runtime",
RouteTableIds=[route_table["RouteTableId"]],
VpcEndpointType="Interface",
)
from prowler.providers.aws.services.vpc.vpc_service import VPC
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with (
mock.patch(
f"{CHECK_MODULE}.vpc_client",
new=VPC(aws_provider),
),
mock.patch(
f"{CHECK_MODULE}.bedrock_client",
new=MOCK_BEDROCK_CLIENT,
),
mock.patch(
f"{CHECK_MODULE}.bedrock_agent_client",
new=MOCK_BEDROCK_AGENT_CLIENT,
),
):
from prowler.providers.aws.services.bedrock.bedrock_vpc_endpoints_configured.bedrock_vpc_endpoints_configured import (
bedrock_vpc_endpoints_configured,
)
check = bedrock_vpc_endpoints_configured()
result = check.execute()
assert len(result) == 2
finding = next(f for f in result if f.resource_id == vpc["VpcId"])
assert finding.status == "FAIL"
assert "Bedrock control plane" in finding.status_extended
assert "Bedrock agent control plane" in finding.status_extended
assert "Bedrock Mantle" in finding.status_extended
assert "Bedrock runtime" not in finding.status_extended
assert "Bedrock agent runtime" not in finding.status_extended
@mock_aws
def test_vpc_unrelated_endpoint_only(self):
"""Test VPC with only an unrelated endpoint (S3) - should FAIL with all services missing."""
ec2_client = client("ec2", region_name=AWS_REGION_US_EAST_1)
vpc = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]
route_table = ec2_client.create_route_table(VpcId=vpc["VpcId"])["RouteTable"]
ec2_client.create_vpc_endpoint(
VpcId=vpc["VpcId"],
ServiceName="com.amazonaws.us-east-1.s3",
RouteTableIds=[route_table["RouteTableId"]],
VpcEndpointType="Gateway",
)
from prowler.providers.aws.services.vpc.vpc_service import VPC
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with (
mock.patch(
f"{CHECK_MODULE}.vpc_client",
new=VPC(aws_provider),
),
mock.patch(
f"{CHECK_MODULE}.bedrock_client",
new=MOCK_BEDROCK_CLIENT,
),
mock.patch(
f"{CHECK_MODULE}.bedrock_agent_client",
new=MOCK_BEDROCK_AGENT_CLIENT,
),
):
from prowler.providers.aws.services.bedrock.bedrock_vpc_endpoints_configured.bedrock_vpc_endpoints_configured import (
bedrock_vpc_endpoints_configured,
)
check = bedrock_vpc_endpoints_configured()
result = check.execute()
assert len(result) == 2
finding = next(f for f in result if f.resource_id == vpc["VpcId"])
assert finding.region == AWS_REGION_US_EAST_1
assert finding.status == "FAIL"
assert "Bedrock control plane" in finding.status_extended
assert "Bedrock runtime" in finding.status_extended
assert "Bedrock agent control plane" in finding.status_extended
assert "Bedrock agent runtime" in finding.status_extended
assert "Bedrock Mantle" in finding.status_extended
@mock_aws
def test_vpc_only_bedrock_mantle_endpoint(self):
"""Test VPC with only Bedrock Mantle endpoint - should FAIL with four services missing."""
ec2_client = client("ec2", region_name=AWS_REGION_US_EAST_1)
vpc = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"]
route_table = ec2_client.create_route_table(VpcId=vpc["VpcId"])["RouteTable"]
ec2_client.create_vpc_endpoint(
VpcId=vpc["VpcId"],
ServiceName="com.amazonaws.us-east-1.bedrock-mantle",
RouteTableIds=[route_table["RouteTableId"]],
VpcEndpointType="Interface",
)
from prowler.providers.aws.services.vpc.vpc_service import VPC
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with (
mock.patch(
f"{CHECK_MODULE}.vpc_client",
new=VPC(aws_provider),
),
mock.patch(
f"{CHECK_MODULE}.bedrock_client",
new=MOCK_BEDROCK_CLIENT,
),
mock.patch(
f"{CHECK_MODULE}.bedrock_agent_client",
new=MOCK_BEDROCK_AGENT_CLIENT,
),
):
from prowler.providers.aws.services.bedrock.bedrock_vpc_endpoints_configured.bedrock_vpc_endpoints_configured import (
bedrock_vpc_endpoints_configured,
)
check = bedrock_vpc_endpoints_configured()
result = check.execute()
assert len(result) == 2
finding = next(f for f in result if f.resource_id == vpc["VpcId"])
assert finding.status == "FAIL"
assert "Bedrock Mantle" not in finding.status_extended
assert "Bedrock control plane" in finding.status_extended
assert "Bedrock runtime" in finding.status_extended
assert "Bedrock agent control plane" in finding.status_extended
assert "Bedrock agent runtime" in finding.status_extended
@mock_aws
def test_bedrock_activity_via_guardrail(self):
"""Test that Bedrock activity is detected via guardrails."""
ec2_client = client("ec2", region_name=AWS_REGION_US_EAST_1)
vpc_id = ec2_client.describe_vpcs()["Vpcs"][0]["VpcId"]
from prowler.providers.aws.services.vpc.vpc_service import VPC
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
bedrock_with_guardrail = mock.MagicMock(
logging_configurations={
AWS_REGION_US_EAST_1: LoggingConfiguration(enabled=False)
},
guardrails={
"arn:aws:bedrock:us-east-1:123456789012:guardrail/test": Guardrail(
id="test",
name="test-guardrail",
arn="arn:aws:bedrock:us-east-1:123456789012:guardrail/test",
region=AWS_REGION_US_EAST_1,
)
},
)
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with (
mock.patch(
f"{CHECK_MODULE}.vpc_client",
new=VPC(aws_provider),
),
mock.patch(
f"{CHECK_MODULE}.bedrock_client",
new=bedrock_with_guardrail,
),
mock.patch(
f"{CHECK_MODULE}.bedrock_agent_client",
new=MOCK_BEDROCK_AGENT_CLIENT_NO_ACTIVITY,
),
):
from prowler.providers.aws.services.bedrock.bedrock_vpc_endpoints_configured.bedrock_vpc_endpoints_configured import (
bedrock_vpc_endpoints_configured,
)
check = bedrock_vpc_endpoints_configured()
result = check.execute()
# Region has Bedrock activity via guardrail, so VPC should be checked
assert len(result) == 1
assert result[0].resource_id == vpc_id
assert result[0].status == "FAIL"
@@ -0,0 +1,461 @@
from json import dumps
from unittest import mock
from boto3 import client
from moto import mock_aws
from prowler.providers.aws.services.iam.iam_service import IAM
from tests.providers.aws.utils import (
ADMINISTRATOR_ROLE_ASSUME_ROLE_POLICY,
AWS_REGION_EU_WEST_1,
set_mocked_aws_provider,
)
CHECK_MODULE_PATH = "prowler.providers.aws.services.iam.iam_inline_policy_no_wildcard_marketplace_subscribe.iam_inline_policy_no_wildcard_marketplace_subscribe"
class Test_iam_inline_policy_no_wildcard_marketplace_subscribe:
@mock_aws
def test_inline_policy_allows_marketplace_subscribe_on_all_resources(self):
"""FAIL: Inline policy allows aws-marketplace:Subscribe on Resource:*."""
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1)
role_name = "test_role"
role_arn = iam_client.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=dumps(ADMINISTRATOR_ROLE_ASSUME_ROLE_POLICY),
)["Role"]["Arn"]
policy_name = "marketplace_subscribe_wildcard"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "aws-marketplace:Subscribe",
"Resource": "*",
},
],
}
iam_client.put_role_policy(
RoleName=role_name,
PolicyName=policy_name,
PolicyDocument=dumps(policy_document),
)
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
f"{CHECK_MODULE_PATH}.iam_client",
new=IAM(aws_provider),
),
):
from prowler.providers.aws.services.iam.iam_inline_policy_no_wildcard_marketplace_subscribe.iam_inline_policy_no_wildcard_marketplace_subscribe import (
iam_inline_policy_no_wildcard_marketplace_subscribe,
)
check = iam_inline_policy_no_wildcard_marketplace_subscribe()
result = check.execute()
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Inline policy {policy_name} attached to role {role_name} allows 'aws-marketplace:Subscribe' on all resources."
)
assert result[0].resource_id == f"{role_name}/{policy_name}"
assert result[0].resource_arn == role_arn
assert result[0].region == "eu-west-1"
@mock_aws
def test_inline_policy_allows_marketplace_wildcard_action(self):
"""FAIL: Inline policy allows aws-marketplace:* on Resource:*."""
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1)
role_name = "test_role"
role_arn = iam_client.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=dumps(ADMINISTRATOR_ROLE_ASSUME_ROLE_POLICY),
)["Role"]["Arn"]
policy_name = "marketplace_all_actions"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "aws-marketplace:*",
"Resource": "*",
},
],
}
iam_client.put_role_policy(
RoleName=role_name,
PolicyName=policy_name,
PolicyDocument=dumps(policy_document),
)
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
f"{CHECK_MODULE_PATH}.iam_client",
new=IAM(aws_provider),
),
):
from prowler.providers.aws.services.iam.iam_inline_policy_no_wildcard_marketplace_subscribe.iam_inline_policy_no_wildcard_marketplace_subscribe import (
iam_inline_policy_no_wildcard_marketplace_subscribe,
)
check = iam_inline_policy_no_wildcard_marketplace_subscribe()
result = check.execute()
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Inline policy {policy_name} attached to role {role_name} allows 'aws-marketplace:Subscribe' on all resources."
)
assert result[0].resource_id == f"{role_name}/{policy_name}"
assert result[0].resource_arn == role_arn
assert result[0].region == "eu-west-1"
@mock_aws
def test_inline_policy_scoped_resource(self):
"""PASS: Inline policy allows aws-marketplace:Subscribe on a specific resource."""
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1)
role_name = "test_role"
role_arn = iam_client.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=dumps(ADMINISTRATOR_ROLE_ASSUME_ROLE_POLICY),
)["Role"]["Arn"]
policy_name = "marketplace_subscribe_scoped"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "aws-marketplace:Subscribe",
"Resource": "arn:aws:aws-marketplace::123456789012:product/example-product-id",
},
],
}
iam_client.put_role_policy(
RoleName=role_name,
PolicyName=policy_name,
PolicyDocument=dumps(policy_document),
)
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
f"{CHECK_MODULE_PATH}.iam_client",
new=IAM(aws_provider),
),
):
from prowler.providers.aws.services.iam.iam_inline_policy_no_wildcard_marketplace_subscribe.iam_inline_policy_no_wildcard_marketplace_subscribe import (
iam_inline_policy_no_wildcard_marketplace_subscribe,
)
check = iam_inline_policy_no_wildcard_marketplace_subscribe()
result = check.execute()
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Inline policy {policy_name} attached to role {role_name} does not allow 'aws-marketplace:Subscribe' on all resources."
)
assert result[0].resource_id == f"{role_name}/{policy_name}"
assert result[0].resource_arn == role_arn
assert result[0].region == "eu-west-1"
@mock_aws
def test_inline_policy_unrelated_action(self):
"""PASS: Inline policy allows an unrelated action on Resource:*."""
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1)
role_name = "test_role"
role_arn = iam_client.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=dumps(ADMINISTRATOR_ROLE_ASSUME_ROLE_POLICY),
)["Role"]["Arn"]
policy_name = "ec2_full_access"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ec2:*",
"Resource": "*",
},
],
}
iam_client.put_role_policy(
RoleName=role_name,
PolicyName=policy_name,
PolicyDocument=dumps(policy_document),
)
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
f"{CHECK_MODULE_PATH}.iam_client",
new=IAM(aws_provider),
),
):
from prowler.providers.aws.services.iam.iam_inline_policy_no_wildcard_marketplace_subscribe.iam_inline_policy_no_wildcard_marketplace_subscribe import (
iam_inline_policy_no_wildcard_marketplace_subscribe,
)
check = iam_inline_policy_no_wildcard_marketplace_subscribe()
result = check.execute()
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Inline policy {policy_name} attached to role {role_name} does not allow 'aws-marketplace:Subscribe' on all resources."
)
assert result[0].resource_id == f"{role_name}/{policy_name}"
assert result[0].resource_arn == role_arn
assert result[0].region == "eu-west-1"
@mock_aws
def test_inline_policy_deny_overrides_allow(self):
"""PASS: Allow + Deny on * for same action — Deny wins."""
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1)
role_name = "test_role"
role_arn = iam_client.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=dumps(ADMINISTRATOR_ROLE_ASSUME_ROLE_POLICY),
)["Role"]["Arn"]
policy_name = "marketplace_subscribe_denied"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "aws-marketplace:Subscribe",
"Resource": "*",
},
{
"Effect": "Deny",
"Action": "aws-marketplace:Subscribe",
"Resource": "*",
},
],
}
iam_client.put_role_policy(
RoleName=role_name,
PolicyName=policy_name,
PolicyDocument=dumps(policy_document),
)
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
f"{CHECK_MODULE_PATH}.iam_client",
new=IAM(aws_provider),
),
):
from prowler.providers.aws.services.iam.iam_inline_policy_no_wildcard_marketplace_subscribe.iam_inline_policy_no_wildcard_marketplace_subscribe import (
iam_inline_policy_no_wildcard_marketplace_subscribe,
)
check = iam_inline_policy_no_wildcard_marketplace_subscribe()
result = check.execute()
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Inline policy {policy_name} attached to role {role_name} does not allow 'aws-marketplace:Subscribe' on all resources."
)
assert result[0].resource_id == f"{role_name}/{policy_name}"
assert result[0].resource_arn == role_arn
assert result[0].region == "eu-west-1"
@mock_aws
def test_inline_policy_deny_specific_resource_does_not_override(self):
"""FAIL: Deny on specific resource does not negate Allow on *."""
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1)
role_name = "test_role"
role_arn = iam_client.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=dumps(ADMINISTRATOR_ROLE_ASSUME_ROLE_POLICY),
)["Role"]["Arn"]
policy_name = "deny_specific_only"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "aws-marketplace:Subscribe",
"Resource": "*",
},
{
"Effect": "Deny",
"Action": "aws-marketplace:Subscribe",
"Resource": "arn:aws:aws-marketplace::123456789012:product/blocked",
},
],
}
iam_client.put_role_policy(
RoleName=role_name,
PolicyName=policy_name,
PolicyDocument=dumps(policy_document),
)
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
f"{CHECK_MODULE_PATH}.iam_client",
new=IAM(aws_provider),
),
):
from prowler.providers.aws.services.iam.iam_inline_policy_no_wildcard_marketplace_subscribe.iam_inline_policy_no_wildcard_marketplace_subscribe import (
iam_inline_policy_no_wildcard_marketplace_subscribe,
)
check = iam_inline_policy_no_wildcard_marketplace_subscribe()
result = check.execute()
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Inline policy {policy_name} attached to role {role_name} allows 'aws-marketplace:Subscribe' on all resources."
)
assert result[0].resource_id == f"{role_name}/{policy_name}"
assert result[0].resource_arn == role_arn
assert result[0].region == "eu-west-1"
@mock_aws
def test_inline_policy_not_action_allows_marketplace_subscribe(self):
"""FAIL: Allow + NotAction excludes only unrelated actions so Subscribe is granted."""
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1)
role_name = "test_role"
role_arn = iam_client.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=dumps(ADMINISTRATOR_ROLE_ASSUME_ROLE_POLICY),
)["Role"]["Arn"]
policy_name = "marketplace_not_action"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"NotAction": "ec2:Describe*",
"Resource": "*",
},
],
}
iam_client.put_role_policy(
RoleName=role_name,
PolicyName=policy_name,
PolicyDocument=dumps(policy_document),
)
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
f"{CHECK_MODULE_PATH}.iam_client",
new=IAM(aws_provider),
),
):
from prowler.providers.aws.services.iam.iam_inline_policy_no_wildcard_marketplace_subscribe.iam_inline_policy_no_wildcard_marketplace_subscribe import (
iam_inline_policy_no_wildcard_marketplace_subscribe,
)
check = iam_inline_policy_no_wildcard_marketplace_subscribe()
result = check.execute()
assert result[0].status == "FAIL"
assert result[0].resource_arn == role_arn
@mock_aws
def test_inline_policy_not_action_excludes_marketplace_subscribe(self):
"""PASS: Allow + NotAction that excludes aws-marketplace:Subscribe does not grant it."""
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
iam_client = client("iam", region_name=AWS_REGION_EU_WEST_1)
role_name = "test_role"
role_arn = iam_client.create_role(
RoleName=role_name,
AssumeRolePolicyDocument=dumps(ADMINISTRATOR_ROLE_ASSUME_ROLE_POLICY),
)["Role"]["Arn"]
policy_name = "marketplace_not_action_excluded"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"NotAction": "aws-marketplace:Subscribe",
"Resource": "*",
},
],
}
iam_client.put_role_policy(
RoleName=role_name,
PolicyName=policy_name,
PolicyDocument=dumps(policy_document),
)
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
f"{CHECK_MODULE_PATH}.iam_client",
new=IAM(aws_provider),
),
):
from prowler.providers.aws.services.iam.iam_inline_policy_no_wildcard_marketplace_subscribe.iam_inline_policy_no_wildcard_marketplace_subscribe import (
iam_inline_policy_no_wildcard_marketplace_subscribe,
)
check = iam_inline_policy_no_wildcard_marketplace_subscribe()
result = check.execute()
assert result[0].status == "PASS"
assert result[0].resource_arn == role_arn
@mock_aws
def test_no_inline_policies(self):
"""No findings when there are no inline policies."""
aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1])
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
f"{CHECK_MODULE_PATH}.iam_client",
new=IAM(aws_provider),
),
):
from prowler.providers.aws.services.iam.iam_inline_policy_no_wildcard_marketplace_subscribe.iam_inline_policy_no_wildcard_marketplace_subscribe import (
iam_inline_policy_no_wildcard_marketplace_subscribe,
)
check = iam_inline_policy_no_wildcard_marketplace_subscribe()
result = check.execute()
assert len(result) == 0
@@ -0,0 +1,509 @@
from json import dumps
from unittest import mock
from boto3 import client
from moto import mock_aws
from prowler.providers.aws.services.iam.iam_service import IAM
from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider
CHECK_MODULE_PATH = "prowler.providers.aws.services.iam.iam_policy_no_wildcard_marketplace_subscribe.iam_policy_no_wildcard_marketplace_subscribe"
class Test_iam_policy_no_wildcard_marketplace_subscribe:
@mock_aws
def test_policy_allows_marketplace_subscribe_on_all_resources(self):
"""FAIL: Policy explicitly allows aws-marketplace:Subscribe on Resource:*."""
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam_client = client("iam")
policy_name = "marketplace_subscribe_wildcard"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "aws-marketplace:Subscribe",
"Resource": "*",
},
],
}
arn = iam_client.create_policy(
PolicyName=policy_name, PolicyDocument=dumps(policy_document)
)["Policy"]["Arn"]
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with mock.patch(
f"{CHECK_MODULE_PATH}.iam_client",
new=IAM(aws_provider),
):
from prowler.providers.aws.services.iam.iam_policy_no_wildcard_marketplace_subscribe.iam_policy_no_wildcard_marketplace_subscribe import (
iam_policy_no_wildcard_marketplace_subscribe,
)
check = iam_policy_no_wildcard_marketplace_subscribe()
result = check.execute()
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Custom Policy {policy_name} allows 'aws-marketplace:Subscribe' on all resources."
)
assert result[0].resource_id == policy_name
assert result[0].resource_arn == arn
assert result[0].region == "us-east-1"
@mock_aws
def test_policy_allows_marketplace_wildcard_action_on_all_resources(self):
"""FAIL: Policy allows aws-marketplace:* on Resource:*."""
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam_client = client("iam")
policy_name = "marketplace_all_actions"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "aws-marketplace:*",
"Resource": "*",
},
],
}
arn = iam_client.create_policy(
PolicyName=policy_name, PolicyDocument=dumps(policy_document)
)["Policy"]["Arn"]
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with mock.patch(
f"{CHECK_MODULE_PATH}.iam_client",
new=IAM(aws_provider),
):
from prowler.providers.aws.services.iam.iam_policy_no_wildcard_marketplace_subscribe.iam_policy_no_wildcard_marketplace_subscribe import (
iam_policy_no_wildcard_marketplace_subscribe,
)
check = iam_policy_no_wildcard_marketplace_subscribe()
result = check.execute()
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Custom Policy {policy_name} allows 'aws-marketplace:Subscribe' on all resources."
)
assert result[0].resource_id == policy_name
assert result[0].resource_arn == arn
assert result[0].region == "us-east-1"
@mock_aws
def test_policy_allows_full_wildcard_on_all_resources(self):
"""FAIL: Policy allows * (all actions) on Resource:*."""
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam_client = client("iam")
policy_name = "full_admin_access"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "*",
"Resource": "*",
},
],
}
arn = iam_client.create_policy(
PolicyName=policy_name, PolicyDocument=dumps(policy_document)
)["Policy"]["Arn"]
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with mock.patch(
f"{CHECK_MODULE_PATH}.iam_client",
new=IAM(aws_provider),
):
from prowler.providers.aws.services.iam.iam_policy_no_wildcard_marketplace_subscribe.iam_policy_no_wildcard_marketplace_subscribe import (
iam_policy_no_wildcard_marketplace_subscribe,
)
check = iam_policy_no_wildcard_marketplace_subscribe()
result = check.execute()
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Custom Policy {policy_name} allows 'aws-marketplace:Subscribe' on all resources."
)
assert result[0].resource_id == policy_name
assert result[0].resource_arn == arn
assert result[0].region == "us-east-1"
@mock_aws
def test_policy_allows_marketplace_subscribe_on_specific_resource(self):
"""PASS: Policy allows aws-marketplace:Subscribe on a specific resource ARN."""
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam_client = client("iam")
policy_name = "marketplace_subscribe_scoped"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "aws-marketplace:Subscribe",
"Resource": "arn:aws:aws-marketplace::123456789012:product/example-product-id",
},
],
}
arn = iam_client.create_policy(
PolicyName=policy_name, PolicyDocument=dumps(policy_document)
)["Policy"]["Arn"]
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with mock.patch(
f"{CHECK_MODULE_PATH}.iam_client",
new=IAM(aws_provider),
):
from prowler.providers.aws.services.iam.iam_policy_no_wildcard_marketplace_subscribe.iam_policy_no_wildcard_marketplace_subscribe import (
iam_policy_no_wildcard_marketplace_subscribe,
)
check = iam_policy_no_wildcard_marketplace_subscribe()
result = check.execute()
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Custom Policy {policy_name} does not allow 'aws-marketplace:Subscribe' on all resources."
)
assert result[0].resource_id == policy_name
assert result[0].resource_arn == arn
assert result[0].region == "us-east-1"
@mock_aws
def test_policy_unrelated_action_on_all_resources(self):
"""PASS: Policy allows an unrelated action on Resource:*."""
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam_client = client("iam")
policy_name = "ec2_full_access"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ec2:*",
"Resource": "*",
},
],
}
arn = iam_client.create_policy(
PolicyName=policy_name, PolicyDocument=dumps(policy_document)
)["Policy"]["Arn"]
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with mock.patch(
f"{CHECK_MODULE_PATH}.iam_client",
new=IAM(aws_provider),
):
from prowler.providers.aws.services.iam.iam_policy_no_wildcard_marketplace_subscribe.iam_policy_no_wildcard_marketplace_subscribe import (
iam_policy_no_wildcard_marketplace_subscribe,
)
check = iam_policy_no_wildcard_marketplace_subscribe()
result = check.execute()
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Custom Policy {policy_name} does not allow 'aws-marketplace:Subscribe' on all resources."
)
assert result[0].resource_id == policy_name
assert result[0].resource_arn == arn
assert result[0].region == "us-east-1"
@mock_aws
def test_policy_marketplace_subscribe_denied_on_all_resources(self):
"""PASS: Allow + Deny on * for same action — Deny wins."""
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam_client = client("iam")
policy_name = "marketplace_subscribe_denied"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "aws-marketplace:Subscribe",
"Resource": "*",
},
{
"Effect": "Deny",
"Action": "aws-marketplace:Subscribe",
"Resource": "*",
},
],
}
arn = iam_client.create_policy(
PolicyName=policy_name, PolicyDocument=dumps(policy_document)
)["Policy"]["Arn"]
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with mock.patch(
f"{CHECK_MODULE_PATH}.iam_client",
new=IAM(aws_provider),
):
from prowler.providers.aws.services.iam.iam_policy_no_wildcard_marketplace_subscribe.iam_policy_no_wildcard_marketplace_subscribe import (
iam_policy_no_wildcard_marketplace_subscribe,
)
check = iam_policy_no_wildcard_marketplace_subscribe()
result = check.execute()
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Custom Policy {policy_name} does not allow 'aws-marketplace:Subscribe' on all resources."
)
assert result[0].resource_id == policy_name
assert result[0].resource_arn == arn
assert result[0].region == "us-east-1"
@mock_aws
def test_policy_deny_specific_resource_does_not_override_allow_all(self):
"""FAIL: Deny on a specific resource does not negate Allow on Resource:*."""
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam_client = client("iam")
policy_name = "deny_specific_resource"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "aws-marketplace:Subscribe",
"Resource": "*",
},
{
"Effect": "Deny",
"Action": "aws-marketplace:Subscribe",
"Resource": "arn:aws:aws-marketplace::123456789012:product/blocked-product",
},
],
}
arn = iam_client.create_policy(
PolicyName=policy_name, PolicyDocument=dumps(policy_document)
)["Policy"]["Arn"]
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with mock.patch(
f"{CHECK_MODULE_PATH}.iam_client",
new=IAM(aws_provider),
):
from prowler.providers.aws.services.iam.iam_policy_no_wildcard_marketplace_subscribe.iam_policy_no_wildcard_marketplace_subscribe import (
iam_policy_no_wildcard_marketplace_subscribe,
)
check = iam_policy_no_wildcard_marketplace_subscribe()
result = check.execute()
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Custom Policy {policy_name} allows 'aws-marketplace:Subscribe' on all resources."
)
assert result[0].resource_id == policy_name
assert result[0].resource_arn == arn
assert result[0].region == "us-east-1"
@mock_aws
def test_policy_deny_unrelated_action_does_not_override_allow(self):
"""FAIL: Deny for a different action does not override the Allow for Subscribe."""
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam_client = client("iam")
policy_name = "deny_unrelated_action"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "aws-marketplace:Subscribe",
"Resource": "*",
},
{
"Effect": "Deny",
"Action": "ec2:TerminateInstances",
"Resource": "*",
},
],
}
arn = iam_client.create_policy(
PolicyName=policy_name, PolicyDocument=dumps(policy_document)
)["Policy"]["Arn"]
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with mock.patch(
f"{CHECK_MODULE_PATH}.iam_client",
new=IAM(aws_provider),
):
from prowler.providers.aws.services.iam.iam_policy_no_wildcard_marketplace_subscribe.iam_policy_no_wildcard_marketplace_subscribe import (
iam_policy_no_wildcard_marketplace_subscribe,
)
check = iam_policy_no_wildcard_marketplace_subscribe()
result = check.execute()
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Custom Policy {policy_name} allows 'aws-marketplace:Subscribe' on all resources."
)
assert result[0].resource_id == policy_name
assert result[0].resource_arn == arn
assert result[0].region == "us-east-1"
@mock_aws
def test_policy_case_insensitive_action_matching(self):
"""FAIL: Mixed-case action still matches aws-marketplace:Subscribe."""
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam_client = client("iam")
policy_name = "mixed_case_subscribe"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "AWS-Marketplace:SUBSCRIBE",
"Resource": "*",
},
],
}
arn = iam_client.create_policy(
PolicyName=policy_name, PolicyDocument=dumps(policy_document)
)["Policy"]["Arn"]
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with mock.patch(
f"{CHECK_MODULE_PATH}.iam_client",
new=IAM(aws_provider),
):
from prowler.providers.aws.services.iam.iam_policy_no_wildcard_marketplace_subscribe.iam_policy_no_wildcard_marketplace_subscribe import (
iam_policy_no_wildcard_marketplace_subscribe,
)
check = iam_policy_no_wildcard_marketplace_subscribe()
result = check.execute()
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Custom Policy {policy_name} allows 'aws-marketplace:Subscribe' on all resources."
)
assert result[0].resource_id == policy_name
assert result[0].resource_arn == arn
assert result[0].region == "us-east-1"
@mock_aws
def test_policy_not_action_allows_marketplace_subscribe(self):
"""FAIL: Allow + NotAction excludes only unrelated actions so Subscribe is granted."""
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam_client = client("iam")
policy_name = "marketplace_not_action"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"NotAction": "ec2:Describe*",
"Resource": "*",
},
],
}
arn = iam_client.create_policy(
PolicyName=policy_name, PolicyDocument=dumps(policy_document)
)["Policy"]["Arn"]
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with mock.patch(
f"{CHECK_MODULE_PATH}.iam_client",
new=IAM(aws_provider),
):
from prowler.providers.aws.services.iam.iam_policy_no_wildcard_marketplace_subscribe.iam_policy_no_wildcard_marketplace_subscribe import (
iam_policy_no_wildcard_marketplace_subscribe,
)
check = iam_policy_no_wildcard_marketplace_subscribe()
result = check.execute()
assert result[0].status == "FAIL"
assert result[0].resource_arn == arn
@mock_aws
def test_policy_not_action_excludes_marketplace_subscribe(self):
"""PASS: Allow + NotAction that excludes aws-marketplace:Subscribe does not grant it."""
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam_client = client("iam")
policy_name = "marketplace_not_action_excluded"
policy_document = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"NotAction": "aws-marketplace:Subscribe",
"Resource": "*",
},
],
}
arn = iam_client.create_policy(
PolicyName=policy_name, PolicyDocument=dumps(policy_document)
)["Policy"]["Arn"]
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with mock.patch(
f"{CHECK_MODULE_PATH}.iam_client",
new=IAM(aws_provider),
):
from prowler.providers.aws.services.iam.iam_policy_no_wildcard_marketplace_subscribe.iam_policy_no_wildcard_marketplace_subscribe import (
iam_policy_no_wildcard_marketplace_subscribe,
)
check = iam_policy_no_wildcard_marketplace_subscribe()
result = check.execute()
assert result[0].status == "PASS"
assert result[0].resource_arn == arn
@mock_aws
def test_no_custom_policies(self):
"""No findings when there are no custom IAM policies."""
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
):
with mock.patch(
f"{CHECK_MODULE_PATH}.iam_client",
new=IAM(aws_provider),
):
from prowler.providers.aws.services.iam.iam_policy_no_wildcard_marketplace_subscribe.iam_policy_no_wildcard_marketplace_subscribe import (
iam_policy_no_wildcard_marketplace_subscribe,
)
check = iam_policy_no_wildcard_marketplace_subscribe()
result = check.execute()
assert len(result) == 0
@@ -0,0 +1,240 @@
from datetime import datetime, timedelta, timezone
from unittest import mock
from moto import mock_aws
from tests.providers.aws.utils import (
AWS_ACCOUNT_NUMBER,
AWS_REGION_US_EAST_1,
set_mocked_aws_provider,
)
IAM_ROLE_NAME = "test-role"
IAM_ROLE_ARN = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:role/{IAM_ROLE_NAME}"
ROLE_DATA = (IAM_ROLE_NAME, IAM_ROLE_ARN)
CHECK_MODULE = (
"prowler.providers.aws.services.iam."
"iam_role_access_not_stale_to_bedrock.iam_role_access_not_stale_to_bedrock"
)
class Test_iam_role_access_not_stale_to_bedrock:
@mock_aws
def test_no_roles_with_bedrock_permissions(self):
"""No findings when no roles have Bedrock in last accessed services."""
from prowler.providers.aws.services.iam.iam_service import IAM
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam = IAM(aws_provider)
iam.role_last_accessed_services = {}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
):
from prowler.providers.aws.services.iam.iam_role_access_not_stale_to_bedrock.iam_role_access_not_stale_to_bedrock import (
iam_role_access_not_stale_to_bedrock,
)
check = iam_role_access_not_stale_to_bedrock()
assert len(check.execute()) == 0
@mock_aws
def test_role_bedrock_access_stale(self):
"""FAIL when a role last accessed Bedrock more than 60 days ago."""
from prowler.providers.aws.services.iam.iam_service import IAM, Role
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam = IAM(aws_provider)
mock_role = Role(
name=IAM_ROLE_NAME,
arn=IAM_ROLE_ARN,
assume_role_policy={},
is_service_role=False,
attached_policies=[],
inline_policies=[],
)
iam.roles = [mock_role]
last_authenticated = datetime.now(timezone.utc) - timedelta(days=120)
iam.role_last_accessed_services = {
ROLE_DATA: [
{
"ServiceNamespace": "bedrock",
"ServiceName": "Amazon Bedrock",
"LastAuthenticated": last_authenticated,
},
]
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
):
from prowler.providers.aws.services.iam.iam_role_access_not_stale_to_bedrock.iam_role_access_not_stale_to_bedrock import (
iam_role_access_not_stale_to_bedrock,
)
check = iam_role_access_not_stale_to_bedrock()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "has not accessed Bedrock" in result[0].status_extended
assert "Role" in result[0].status_extended
assert IAM_ROLE_NAME in result[0].status_extended
assert result[0].resource_id == IAM_ROLE_NAME
assert result[0].resource_arn == IAM_ROLE_ARN
assert result[0].region == AWS_REGION_US_EAST_1
@mock_aws
def test_role_bedrock_access_recent(self):
"""PASS when a role accessed Bedrock recently."""
from prowler.providers.aws.services.iam.iam_service import IAM, Role
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam = IAM(aws_provider)
mock_role = Role(
name=IAM_ROLE_NAME,
arn=IAM_ROLE_ARN,
assume_role_policy={},
is_service_role=False,
attached_policies=[],
inline_policies=[],
)
iam.roles = [mock_role]
last_authenticated = datetime.now(timezone.utc) - timedelta(days=5)
iam.role_last_accessed_services = {
ROLE_DATA: [
{
"ServiceNamespace": "bedrock",
"ServiceName": "Amazon Bedrock",
"LastAuthenticated": last_authenticated,
},
]
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
):
from prowler.providers.aws.services.iam.iam_role_access_not_stale_to_bedrock.iam_role_access_not_stale_to_bedrock import (
iam_role_access_not_stale_to_bedrock,
)
check = iam_role_access_not_stale_to_bedrock()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert "accessed Bedrock" in result[0].status_extended
assert "Role" in result[0].status_extended
assert IAM_ROLE_NAME in result[0].status_extended
@mock_aws
def test_role_bedrock_never_accessed(self):
"""FAIL when a role has Bedrock permissions but never accessed them."""
from prowler.providers.aws.services.iam.iam_service import IAM, Role
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam = IAM(aws_provider)
mock_role = Role(
name=IAM_ROLE_NAME,
arn=IAM_ROLE_ARN,
assume_role_policy={},
is_service_role=False,
attached_policies=[],
inline_policies=[],
)
iam.roles = [mock_role]
iam.role_last_accessed_services = {
ROLE_DATA: [
{
"ServiceNamespace": "bedrock",
"ServiceName": "Amazon Bedrock",
},
]
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
):
from prowler.providers.aws.services.iam.iam_role_access_not_stale_to_bedrock.iam_role_access_not_stale_to_bedrock import (
iam_role_access_not_stale_to_bedrock,
)
check = iam_role_access_not_stale_to_bedrock()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "has never used them" in result[0].status_extended
assert "Role" in result[0].status_extended
@mock_aws
def test_role_tags_are_populated(self):
"""Verify resource_tags are populated from the role object."""
from prowler.providers.aws.services.iam.iam_service import IAM, Role
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam = IAM(aws_provider)
role_tags = [{"Key": "Team", "Value": "ml-platform"}]
mock_role = Role(
name=IAM_ROLE_NAME,
arn=IAM_ROLE_ARN,
assume_role_policy={},
is_service_role=False,
attached_policies=[],
inline_policies=[],
tags=role_tags,
)
iam.roles = [mock_role]
last_authenticated = datetime.now(timezone.utc) - timedelta(days=5)
iam.role_last_accessed_services = {
ROLE_DATA: [
{
"ServiceNamespace": "bedrock",
"ServiceName": "Amazon Bedrock",
"LastAuthenticated": last_authenticated,
},
]
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
):
from prowler.providers.aws.services.iam.iam_role_access_not_stale_to_bedrock.iam_role_access_not_stale_to_bedrock import (
iam_role_access_not_stale_to_bedrock,
)
check = iam_role_access_not_stale_to_bedrock()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].resource_tags == role_tags
@@ -0,0 +1,449 @@
from datetime import datetime, timedelta, timezone
from unittest import mock
from moto import mock_aws
from tests.providers.aws.utils import (
AWS_ACCOUNT_NUMBER,
AWS_REGION_US_EAST_1,
set_mocked_aws_provider,
)
IAM_USER_NAME = "test-user"
IAM_USER_ARN = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:user/{IAM_USER_NAME}"
USER_DATA = (IAM_USER_NAME, IAM_USER_ARN)
CHECK_MODULE = (
"prowler.providers.aws.services.iam."
"iam_user_access_not_stale_to_bedrock.iam_user_access_not_stale_to_bedrock"
)
class Test_iam_user_access_not_stale_to_bedrock:
@mock_aws
def test_no_users_with_bedrock_permissions(self):
"""No findings when no users have Bedrock in last accessed services."""
from prowler.providers.aws.services.iam.iam_service import IAM
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam = IAM(aws_provider)
iam.last_accessed_services = {}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
):
from prowler.providers.aws.services.iam.iam_user_access_not_stale_to_bedrock.iam_user_access_not_stale_to_bedrock import (
iam_user_access_not_stale_to_bedrock,
)
check = iam_user_access_not_stale_to_bedrock()
assert len(check.execute()) == 0
@mock_aws
def test_user_without_bedrock_permissions(self):
"""User with non-Bedrock services is skipped."""
from prowler.providers.aws.services.iam.iam_service import IAM, User
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam = IAM(aws_provider)
mock_user = User(
name=IAM_USER_NAME,
arn=IAM_USER_ARN,
attached_policies=[],
inline_policies=[],
)
iam.users = [mock_user]
iam.last_accessed_services = {
USER_DATA: [
{"ServiceNamespace": "iam", "ServiceName": "IAM"},
{"ServiceNamespace": "s3", "ServiceName": "S3"},
]
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
):
from prowler.providers.aws.services.iam.iam_user_access_not_stale_to_bedrock.iam_user_access_not_stale_to_bedrock import (
iam_user_access_not_stale_to_bedrock,
)
check = iam_user_access_not_stale_to_bedrock()
assert len(check.execute()) == 0
@mock_aws
def test_user_bedrock_access_recent(self):
"""PASS when user accessed Bedrock within the threshold."""
from prowler.providers.aws.services.iam.iam_service import IAM, User
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam = IAM(aws_provider)
mock_user = User(
name=IAM_USER_NAME,
arn=IAM_USER_ARN,
attached_policies=[],
inline_policies=[],
)
iam.users = [mock_user]
last_authenticated = datetime.now(timezone.utc) - timedelta(days=10)
iam.last_accessed_services = {
USER_DATA: [
{
"ServiceNamespace": "bedrock",
"ServiceName": "Amazon Bedrock",
"LastAuthenticated": last_authenticated,
},
]
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
):
from prowler.providers.aws.services.iam.iam_user_access_not_stale_to_bedrock.iam_user_access_not_stale_to_bedrock import (
iam_user_access_not_stale_to_bedrock,
)
check = iam_user_access_not_stale_to_bedrock()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert "accessed Bedrock" in result[0].status_extended
assert IAM_USER_NAME in result[0].status_extended
assert result[0].resource_id == IAM_USER_NAME
assert result[0].resource_arn == IAM_USER_ARN
assert result[0].region == AWS_REGION_US_EAST_1
@mock_aws
def test_user_bedrock_access_stale(self):
"""FAIL when user last accessed Bedrock more than 60 days ago."""
from prowler.providers.aws.services.iam.iam_service import IAM, User
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam = IAM(aws_provider)
mock_user = User(
name=IAM_USER_NAME,
arn=IAM_USER_ARN,
attached_policies=[],
inline_policies=[],
)
iam.users = [mock_user]
last_authenticated = datetime.now(timezone.utc) - timedelta(days=90)
iam.last_accessed_services = {
USER_DATA: [
{
"ServiceNamespace": "bedrock",
"ServiceName": "Amazon Bedrock",
"LastAuthenticated": last_authenticated,
},
]
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
):
from prowler.providers.aws.services.iam.iam_user_access_not_stale_to_bedrock.iam_user_access_not_stale_to_bedrock import (
iam_user_access_not_stale_to_bedrock,
)
check = iam_user_access_not_stale_to_bedrock()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "has not accessed Bedrock" in result[0].status_extended
assert "90 days" in result[0].status_extended
assert IAM_USER_NAME in result[0].status_extended
@mock_aws
def test_user_bedrock_never_accessed(self):
"""FAIL when user has Bedrock permissions but has never used them."""
from prowler.providers.aws.services.iam.iam_service import IAM, User
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam = IAM(aws_provider)
mock_user = User(
name=IAM_USER_NAME,
arn=IAM_USER_ARN,
attached_policies=[],
inline_policies=[],
)
iam.users = [mock_user]
iam.last_accessed_services = {
USER_DATA: [
{
"ServiceNamespace": "bedrock",
"ServiceName": "Amazon Bedrock",
},
]
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
):
from prowler.providers.aws.services.iam.iam_user_access_not_stale_to_bedrock.iam_user_access_not_stale_to_bedrock import (
iam_user_access_not_stale_to_bedrock,
)
check = iam_user_access_not_stale_to_bedrock()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "has never used them" in result[0].status_extended
@mock_aws
def test_custom_threshold_via_audit_config(self):
"""Custom threshold from audit_config is respected."""
from prowler.providers.aws.services.iam.iam_service import IAM, User
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam = IAM(aws_provider)
iam.audit_config = {"max_unused_bedrock_access_days": 30}
mock_user = User(
name=IAM_USER_NAME,
arn=IAM_USER_ARN,
attached_policies=[],
inline_policies=[],
)
iam.users = [mock_user]
last_authenticated = datetime.now(timezone.utc) - timedelta(days=45)
iam.last_accessed_services = {
USER_DATA: [
{
"ServiceNamespace": "bedrock",
"ServiceName": "Amazon Bedrock",
"LastAuthenticated": last_authenticated,
},
]
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
):
from prowler.providers.aws.services.iam.iam_user_access_not_stale_to_bedrock.iam_user_access_not_stale_to_bedrock import (
iam_user_access_not_stale_to_bedrock,
)
check = iam_user_access_not_stale_to_bedrock()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "45 days" in result[0].status_extended
assert "threshold: 30 days" in result[0].status_extended
@mock_aws
def test_user_bedrock_access_at_exact_threshold(self):
"""PASS when user accessed Bedrock exactly at the 60-day boundary."""
from prowler.providers.aws.services.iam.iam_service import IAM, User
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam = IAM(aws_provider)
mock_user = User(
name=IAM_USER_NAME,
arn=IAM_USER_ARN,
attached_policies=[],
inline_policies=[],
)
iam.users = [mock_user]
last_authenticated = datetime.now(timezone.utc) - timedelta(days=60)
iam.last_accessed_services = {
USER_DATA: [
{
"ServiceNamespace": "bedrock",
"ServiceName": "Amazon Bedrock",
"LastAuthenticated": last_authenticated,
},
]
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
):
from prowler.providers.aws.services.iam.iam_user_access_not_stale_to_bedrock.iam_user_access_not_stale_to_bedrock import (
iam_user_access_not_stale_to_bedrock,
)
check = iam_user_access_not_stale_to_bedrock()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert "60 days ago" in result[0].status_extended
assert "threshold: 60 days" in result[0].status_extended
@mock_aws
def test_user_bedrock_access_one_day_over_threshold(self):
"""FAIL when user accessed Bedrock 61 days ago."""
from prowler.providers.aws.services.iam.iam_service import IAM, User
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam = IAM(aws_provider)
mock_user = User(
name=IAM_USER_NAME,
arn=IAM_USER_ARN,
attached_policies=[],
inline_policies=[],
)
iam.users = [mock_user]
last_authenticated = datetime.now(timezone.utc) - timedelta(days=61)
iam.last_accessed_services = {
USER_DATA: [
{
"ServiceNamespace": "bedrock",
"ServiceName": "Amazon Bedrock",
"LastAuthenticated": last_authenticated,
},
]
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
):
from prowler.providers.aws.services.iam.iam_user_access_not_stale_to_bedrock.iam_user_access_not_stale_to_bedrock import (
iam_user_access_not_stale_to_bedrock,
)
check = iam_user_access_not_stale_to_bedrock()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "61 days" in result[0].status_extended
assert "threshold: 60 days" in result[0].status_extended
@mock_aws
def test_user_bedrock_access_with_string_date(self):
"""PASS when LastAuthenticated is an ISO string instead of a datetime object."""
from prowler.providers.aws.services.iam.iam_service import IAM, User
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam = IAM(aws_provider)
mock_user = User(
name=IAM_USER_NAME,
arn=IAM_USER_ARN,
attached_policies=[],
inline_policies=[],
)
iam.users = [mock_user]
last_access_date = datetime.now(timezone.utc) - timedelta(days=5)
iam.last_accessed_services = {
USER_DATA: [
{
"ServiceNamespace": "bedrock",
"ServiceName": "Amazon Bedrock",
"LastAuthenticated": last_access_date.isoformat(),
},
]
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
):
from prowler.providers.aws.services.iam.iam_user_access_not_stale_to_bedrock.iam_user_access_not_stale_to_bedrock import (
iam_user_access_not_stale_to_bedrock,
)
check = iam_user_access_not_stale_to_bedrock()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
@mock_aws
def test_user_tags_are_populated(self):
"""Verify resource_tags are populated from the user object."""
from prowler.providers.aws.services.iam.iam_service import IAM, User
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam = IAM(aws_provider)
user_tags = [{"Key": "Environment", "Value": "production"}]
mock_user = User(
name=IAM_USER_NAME,
arn=IAM_USER_ARN,
attached_policies=[],
inline_policies=[],
tags=user_tags,
)
iam.users = [mock_user]
last_authenticated = datetime.now(timezone.utc) - timedelta(days=10)
iam.last_accessed_services = {
USER_DATA: [
{
"ServiceNamespace": "bedrock",
"ServiceName": "Amazon Bedrock",
"LastAuthenticated": last_authenticated,
},
]
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
):
from prowler.providers.aws.services.iam.iam_user_access_not_stale_to_bedrock.iam_user_access_not_stale_to_bedrock import (
iam_user_access_not_stale_to_bedrock,
)
check = iam_user_access_not_stale_to_bedrock()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].resource_tags == user_tags
@@ -15,6 +15,7 @@ from prowler.providers.aws.services.iam.lib.policy import (
is_condition_restricting_from_private_ip,
is_condition_restricting_to_trusted_ips,
is_policy_public,
policy_allows_marketplace_subscribe_on_all_resources,
)
TRUSTED_AWS_ACCOUNT_NUMBER = "123456789012"
@@ -137,6 +138,47 @@ class Test_Policy:
assert result == {"s3:GetObject", "s3:ListBucket"}
assert "s3:PutObject" not in result
def test_policy_allows_marketplace_subscribe_conditional_deny_does_not_cancel(self):
"""Conditional deny should not globally cancel a wildcard marketplace allow."""
policy = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "aws-marketplace:Subscribe",
"Resource": "*",
},
{
"Effect": "Deny",
"Action": "aws-marketplace:Subscribe",
"Resource": "*",
"Condition": {"StringEquals": {"aws:RequestedRegion": "us-east-1"}},
},
],
}
assert policy_allows_marketplace_subscribe_on_all_resources(policy)
def test_policy_allows_marketplace_subscribe_unconditional_deny_cancels(self):
"""Unconditional deny on Resource:* should cancel the wildcard marketplace allow."""
policy = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "aws-marketplace:Subscribe",
"Resource": "*",
},
{
"Effect": "Deny",
"Action": "aws-marketplace:Subscribe",
"Resource": "*",
},
],
}
assert not policy_allows_marketplace_subscribe_on_all_resources(policy)
# Test lowercase context key name --> aws
def test_condition_parser_string_equals_aws_SourceAccount_list(self):
condition_statement = {
@@ -0,0 +1,180 @@
from datetime import datetime
from unittest import mock
from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER, AWS_REGION_US_EAST_1
class Test_stepfunctions_statemachine_no_secrets_in_definition:
def test_no_statemachines(self):
stepfunctions_client = mock.MagicMock()
stepfunctions_client.state_machines = {}
stepfunctions_client.audit_config = {"secrets_ignore_patterns": []}
with (
mock.patch(
"prowler.providers.aws.services.stepfunctions.stepfunctions_service.StepFunctions",
stepfunctions_client,
),
mock.patch(
"prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_client",
stepfunctions_client,
),
):
from prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition import (
stepfunctions_statemachine_no_secrets_in_definition,
)
check = stepfunctions_statemachine_no_secrets_in_definition()
result = check.execute()
assert len(result) == 0
def test_statemachine_with_no_definition(self):
stepfunctions_client = mock.MagicMock()
from prowler.providers.aws.services.stepfunctions.stepfunctions_service import (
StateMachine,
StateMachineStatus,
StateMachineType,
)
statemachine_arn = f"arn:aws:states:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:stateMachine:TestStateMachine"
stepfunctions_client.state_machines = {
statemachine_arn: StateMachine(
id="TestStateMachine",
arn=statemachine_arn,
name="TestStateMachine",
status=StateMachineStatus.ACTIVE,
definition=None,
region=AWS_REGION_US_EAST_1,
type=StateMachineType.STANDARD,
creation_date=datetime.now(),
)
}
stepfunctions_client.audit_config = {"secrets_ignore_patterns": []}
with (
mock.patch(
"prowler.providers.aws.services.stepfunctions.stepfunctions_service.StepFunctions",
stepfunctions_client,
),
mock.patch(
"prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_client",
stepfunctions_client,
),
):
from prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition import (
stepfunctions_statemachine_no_secrets_in_definition,
)
check = stepfunctions_statemachine_no_secrets_in_definition()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "No secrets found in Step Functions state machine TestStateMachine definition."
)
assert result[0].region == AWS_REGION_US_EAST_1
assert result[0].resource_id == "TestStateMachine"
assert result[0].resource_arn == statemachine_arn
def test_statemachine_with_no_secrets_in_definition(self):
stepfunctions_client = mock.MagicMock()
from prowler.providers.aws.services.stepfunctions.stepfunctions_service import (
StateMachine,
StateMachineStatus,
StateMachineType,
)
statemachine_arn = f"arn:aws:states:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:stateMachine:TestStateMachine"
stepfunctions_client.state_machines = {
statemachine_arn: StateMachine(
id="TestStateMachine",
arn=statemachine_arn,
name="TestStateMachine",
status=StateMachineStatus.ACTIVE,
definition='{"Comment": "A simple example", "StartAt": "HelloWorld", "States": {"HelloWorld": {"Type": "Pass", "End": true}}}',
region=AWS_REGION_US_EAST_1,
type=StateMachineType.STANDARD,
creation_date=datetime.now(),
)
}
stepfunctions_client.audit_config = {"secrets_ignore_patterns": []}
with (
mock.patch(
"prowler.providers.aws.services.stepfunctions.stepfunctions_service.StepFunctions",
stepfunctions_client,
),
mock.patch(
"prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_client",
stepfunctions_client,
),
):
from prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition import (
stepfunctions_statemachine_no_secrets_in_definition,
)
check = stepfunctions_statemachine_no_secrets_in_definition()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "No secrets found in Step Functions state machine TestStateMachine definition."
)
assert result[0].region == AWS_REGION_US_EAST_1
assert result[0].resource_id == "TestStateMachine"
assert result[0].resource_arn == statemachine_arn
def test_statemachine_with_secrets_in_definition(self):
stepfunctions_client = mock.MagicMock()
from prowler.providers.aws.services.stepfunctions.stepfunctions_service import (
StateMachine,
StateMachineStatus,
StateMachineType,
)
statemachine_arn = f"arn:aws:states:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:stateMachine:TestStateMachine"
stepfunctions_client.state_machines = {
statemachine_arn: StateMachine(
id="TestStateMachine",
arn=statemachine_arn,
name="TestStateMachine",
status=StateMachineStatus.ACTIVE,
definition='{"Comment": "Example with secret", "StartAt": "MyTask", "States": {"MyTask": {"Type": "Task", "Parameters": {"api_key": "AKIAIOSFODNN7EXAMPLE"}, "End": true}}}',
region=AWS_REGION_US_EAST_1,
type=StateMachineType.STANDARD,
creation_date=datetime.now(),
)
}
stepfunctions_client.audit_config = {"secrets_ignore_patterns": []}
with (
mock.patch(
"prowler.providers.aws.services.stepfunctions.stepfunctions_service.StepFunctions",
stepfunctions_client,
),
mock.patch(
"prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_client",
stepfunctions_client,
),
):
from prowler.providers.aws.services.stepfunctions.stepfunctions_statemachine_no_secrets_in_definition.stepfunctions_statemachine_no_secrets_in_definition import (
stepfunctions_statemachine_no_secrets_in_definition,
)
check = stepfunctions_statemachine_no_secrets_in_definition()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "TestStateMachine" in result[0].status_extended
assert result[0].region == AWS_REGION_US_EAST_1
assert result[0].resource_id == "TestStateMachine"
assert result[0].resource_arn == statemachine_arn
@@ -0,0 +1,46 @@
from prowler.providers.googleworkspace.lib.service.service import GoogleWorkspaceService
class TestIsCustomerLevelPolicy:
def test_no_policy_query(self):
"""Policy without policyQuery is customer-level"""
assert GoogleWorkspaceService._is_customer_level_policy({}) is True
def test_empty_policy_query(self):
"""Policy with empty policyQuery is customer-level"""
assert (
GoogleWorkspaceService._is_customer_level_policy({"policyQuery": {}})
is True
)
def test_org_unit_targeted(self):
"""Policy targeting a specific OU is not customer-level"""
assert (
GoogleWorkspaceService._is_customer_level_policy(
{"policyQuery": {"orgUnit": "orgUnits/abc123"}}
)
is False
)
def test_group_targeted(self):
"""Policy targeting a specific group is not customer-level"""
assert (
GoogleWorkspaceService._is_customer_level_policy(
{"policyQuery": {"group": "groups/xyz789"}}
)
is False
)
def test_org_unit_and_group_targeted(self):
"""Policy targeting both OU and group is not customer-level"""
assert (
GoogleWorkspaceService._is_customer_level_policy(
{
"policyQuery": {
"orgUnit": "orgUnits/abc123",
"group": "groups/xyz789",
}
}
)
is False
)
@@ -211,6 +211,86 @@ class TestCalendarService:
assert calendar.policies.secondary_calendar_external_sharing is None
assert calendar.policies.external_invitations_warning is None
def test_calendar_fetch_policies_ignores_ou_and_group_level(self):
"""Test that OU-level and group-level policies are skipped, only customer-level used"""
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()
# Response includes policies at different org levels:
# customer-level (no policyQuery), OU-level, and group-level
mock_policies_list.execute.return_value = {
"policies": [
{
# Customer-level: no policyQuery → should be used
"setting": {
"type": "settings/calendar.primary_calendar_max_allowed_external_sharing",
"value": {
"maxAllowedExternalSharing": "EXTERNAL_FREE_BUSY_ONLY"
},
}
},
{
# OU-level: has policyQuery.orgUnit → should be skipped
"policyQuery": {"orgUnit": "orgUnits/sales_team"},
"setting": {
"type": "settings/calendar.primary_calendar_max_allowed_external_sharing",
"value": {
"maxAllowedExternalSharing": "EXTERNAL_ALL_INFO_READ_WRITE"
},
},
},
{
# Group-level: has policyQuery.group → should be skipped
"policyQuery": {"group": "groups/contractors"},
"setting": {
"type": "settings/calendar.external_invitations",
"value": {"warnOnInvite": False},
},
},
{
# Customer-level: no policyQuery → should be used
"setting": {
"type": "settings/calendar.external_invitations",
"value": {"warnOnInvite": True},
}
},
]
}
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.calendar.calendar_service.GoogleWorkspaceService._build_service",
return_value=mock_service,
),
):
from prowler.providers.googleworkspace.services.calendar.calendar_service import (
Calendar,
)
calendar = Calendar(mock_provider)
assert calendar.policies_fetched is True
# Customer-level values should be stored
assert (
calendar.policies.primary_calendar_external_sharing
== "EXTERNAL_FREE_BUSY_ONLY"
)
assert calendar.policies.external_invitations_warning is True
# OU-level value (EXTERNAL_ALL_INFO_READ_WRITE) should NOT have overwritten
# Group-level value (warnOnInvite: False) should NOT have overwritten
def test_calendar_policies_model(self):
"""Test CalendarPolicies Pydantic model"""
from prowler.providers.googleworkspace.services.calendar.calendar_service import (
@@ -0,0 +1,156 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.drive.drive_service import DrivePolicies
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
DOMAIN,
set_mocked_googleworkspace_provider,
)
class TestDriveAccessCheckerRecipientsOnly:
def test_pass_recipients_only(self):
"""Test PASS when Access Checker is set to recipients 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.drive.drive_access_checker_recipients_only.drive_access_checker_recipients_only.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_access_checker_recipients_only.drive_access_checker_recipients_only import (
drive_access_checker_recipients_only,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(
access_checker_suggestions="RECIPIENTS_ONLY"
)
check = drive_access_checker_recipients_only()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "recipients only" in findings[0].status_extended
assert findings[0].resource_name == DOMAIN
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_recipients_or_audience(self):
"""Test FAIL when Access Checker allows audience suggestions"""
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.drive.drive_access_checker_recipients_only.drive_access_checker_recipients_only.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_access_checker_recipients_only.drive_access_checker_recipients_only import (
drive_access_checker_recipients_only,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(
access_checker_suggestions="RECIPIENTS_OR_AUDIENCE"
)
check = drive_access_checker_recipients_only()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "RECIPIENTS_OR_AUDIENCE" in findings[0].status_extended
def test_fail_recipients_or_audience_or_public(self):
"""Test FAIL when Access Checker allows public suggestions"""
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.drive.drive_access_checker_recipients_only.drive_access_checker_recipients_only.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_access_checker_recipients_only.drive_access_checker_recipients_only import (
drive_access_checker_recipients_only,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(
access_checker_suggestions="RECIPIENTS_OR_AUDIENCE_OR_PUBLIC"
)
check = drive_access_checker_recipients_only()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "RECIPIENTS_OR_AUDIENCE_OR_PUBLIC" in findings[0].status_extended
def test_fail_no_policy_set(self):
"""Test FAIL when no explicit policy is set (None) but fetch succeeded"""
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.drive.drive_access_checker_recipients_only.drive_access_checker_recipients_only.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_access_checker_recipients_only.drive_access_checker_recipients_only import (
drive_access_checker_recipients_only,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(access_checker_suggestions=None)
check = drive_access_checker_recipients_only()
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.drive.drive_access_checker_recipients_only.drive_access_checker_recipients_only.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_access_checker_recipients_only.drive_access_checker_recipients_only import (
drive_access_checker_recipients_only,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = False
mock_drive_client.policies = DrivePolicies()
check = drive_access_checker_recipients_only()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,122 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.drive.drive_service import DrivePolicies
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
DOMAIN,
set_mocked_googleworkspace_provider,
)
class TestDriveDesktopAccessDisabled:
def test_pass_desktop_disabled(self):
"""Test PASS when Drive for desktop 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.drive.drive_desktop_access_disabled.drive_desktop_access_disabled.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_desktop_access_disabled.drive_desktop_access_disabled import (
drive_desktop_access_disabled,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(allow_drive_for_desktop=False)
check = drive_desktop_access_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 == DOMAIN
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_desktop_enabled(self):
"""Test FAIL when Drive for desktop 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.drive.drive_desktop_access_disabled.drive_desktop_access_disabled.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_desktop_access_disabled.drive_desktop_access_disabled import (
drive_desktop_access_disabled,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(allow_drive_for_desktop=True)
check = drive_desktop_access_disabled()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "enabled" in findings[0].status_extended
def test_fail_no_policy_set(self):
"""Test FAIL when no explicit policy is set (None) but fetch succeeded"""
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.drive.drive_desktop_access_disabled.drive_desktop_access_disabled.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_desktop_access_disabled.drive_desktop_access_disabled import (
drive_desktop_access_disabled,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(allow_drive_for_desktop=None)
check = drive_desktop_access_disabled()
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.drive.drive_desktop_access_disabled.drive_desktop_access_disabled.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_desktop_access_disabled.drive_desktop_access_disabled import (
drive_desktop_access_disabled,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = False
mock_drive_client.policies = DrivePolicies()
check = drive_desktop_access_disabled()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,122 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.drive.drive_service import DrivePolicies
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
DOMAIN,
set_mocked_googleworkspace_provider,
)
class TestDriveExternalSharingWarnUsers:
def test_pass_warning_enabled(self):
"""Test PASS when external sharing warning 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.drive.drive_external_sharing_warn_users.drive_external_sharing_warn_users.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_external_sharing_warn_users.drive_external_sharing_warn_users import (
drive_external_sharing_warn_users,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(warn_for_external_sharing=True)
check = drive_external_sharing_warn_users()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "enabled" in findings[0].status_extended
assert findings[0].resource_name == DOMAIN
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_warning_disabled(self):
"""Test FAIL when external sharing warning 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.drive.drive_external_sharing_warn_users.drive_external_sharing_warn_users.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_external_sharing_warn_users.drive_external_sharing_warn_users import (
drive_external_sharing_warn_users,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(warn_for_external_sharing=False)
check = drive_external_sharing_warn_users()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "disabled" in findings[0].status_extended
def test_fail_no_policy_set(self):
"""Test FAIL when no explicit policy is set (None) but fetch succeeded"""
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.drive.drive_external_sharing_warn_users.drive_external_sharing_warn_users.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_external_sharing_warn_users.drive_external_sharing_warn_users import (
drive_external_sharing_warn_users,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(warn_for_external_sharing=None)
check = drive_external_sharing_warn_users()
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.drive.drive_external_sharing_warn_users.drive_external_sharing_warn_users.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_external_sharing_warn_users.drive_external_sharing_warn_users import (
drive_external_sharing_warn_users,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = False
mock_drive_client.policies = DrivePolicies()
check = drive_external_sharing_warn_users()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,158 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.drive.drive_service import DrivePolicies
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
DOMAIN,
set_mocked_googleworkspace_provider,
)
class TestDriveInternalUsersDistributeContent:
def test_pass_eligible_internal_users(self):
"""Test PASS when content distribution is restricted to eligible internal users"""
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.drive.drive_internal_users_distribute_content.drive_internal_users_distribute_content.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_internal_users_distribute_content.drive_internal_users_distribute_content import (
drive_internal_users_distribute_content,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(
allowed_parties_for_distributing_content="ELIGIBLE_INTERNAL_USERS"
)
check = drive_internal_users_distribute_content()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "ELIGIBLE_INTERNAL_USERS" in findings[0].status_extended
assert findings[0].resource_name == DOMAIN
assert findings[0].customer_id == CUSTOMER_ID
def test_pass_none_allowed(self):
"""Test PASS when content distribution is set to NONE (nobody can distribute)"""
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.drive.drive_internal_users_distribute_content.drive_internal_users_distribute_content.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_internal_users_distribute_content.drive_internal_users_distribute_content import (
drive_internal_users_distribute_content,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(
allowed_parties_for_distributing_content="NONE"
)
check = drive_internal_users_distribute_content()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "NONE" in findings[0].status_extended
def test_fail_all_eligible_users(self):
"""Test FAIL when all users (including external) can distribute content"""
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.drive.drive_internal_users_distribute_content.drive_internal_users_distribute_content.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_internal_users_distribute_content.drive_internal_users_distribute_content import (
drive_internal_users_distribute_content,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(
allowed_parties_for_distributing_content="ALL_ELIGIBLE_USERS"
)
check = drive_internal_users_distribute_content()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "ALL_ELIGIBLE_USERS" in findings[0].status_extended
def test_fail_no_policy_set(self):
"""Test FAIL when no explicit policy is set (None) but fetch succeeded"""
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.drive.drive_internal_users_distribute_content.drive_internal_users_distribute_content.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_internal_users_distribute_content.drive_internal_users_distribute_content import (
drive_internal_users_distribute_content,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(
allowed_parties_for_distributing_content=None
)
check = drive_internal_users_distribute_content()
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.drive.drive_internal_users_distribute_content.drive_internal_users_distribute_content.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_internal_users_distribute_content.drive_internal_users_distribute_content import (
drive_internal_users_distribute_content,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = False
mock_drive_client.policies = DrivePolicies()
check = drive_internal_users_distribute_content()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,122 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.drive.drive_service import DrivePolicies
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
DOMAIN,
set_mocked_googleworkspace_provider,
)
class TestDrivePublishingFilesDisabled:
def test_pass_publishing_disabled(self):
"""Test PASS when publishing files to web 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.drive.drive_publishing_files_disabled.drive_publishing_files_disabled.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_publishing_files_disabled.drive_publishing_files_disabled import (
drive_publishing_files_disabled,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(allow_publishing_files=False)
check = drive_publishing_files_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 == DOMAIN
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_publishing_enabled(self):
"""Test FAIL when publishing files to web 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.drive.drive_publishing_files_disabled.drive_publishing_files_disabled.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_publishing_files_disabled.drive_publishing_files_disabled import (
drive_publishing_files_disabled,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(allow_publishing_files=True)
check = drive_publishing_files_disabled()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "enabled" in findings[0].status_extended
def test_fail_no_policy_set(self):
"""Test FAIL when no explicit policy is set (None) but fetch succeeded"""
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.drive.drive_publishing_files_disabled.drive_publishing_files_disabled.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_publishing_files_disabled.drive_publishing_files_disabled import (
drive_publishing_files_disabled,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(allow_publishing_files=None)
check = drive_publishing_files_disabled()
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.drive.drive_publishing_files_disabled.drive_publishing_files_disabled.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_publishing_files_disabled.drive_publishing_files_disabled import (
drive_publishing_files_disabled,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = False
mock_drive_client.policies = DrivePolicies()
check = drive_publishing_files_disabled()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,349 @@
from unittest.mock import MagicMock, patch
from tests.providers.googleworkspace.googleworkspace_fixtures import (
set_mocked_googleworkspace_provider,
)
class TestDriveService:
def test_drive_fetch_policies_all_settings(self):
"""Test fetching all 3 Drive and Docs policy settings 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()
mock_policies_list = MagicMock()
# Mock the actual Cloud Identity Policy API v1 response shape:
# - "type" (not "name"), prefixed with "settings/"
# - inner value field names are camelCase
mock_policies_list.execute.return_value = {
"policies": [
{
"setting": {
"type": "settings/drive_and_docs.external_sharing",
"value": {
"externalSharingMode": "ALLOWLISTED_DOMAINS",
"warnForExternalSharing": True,
"warnForSharingOutsideAllowlistedDomains": True,
"allowPublishingFiles": False,
"accessCheckerSuggestions": "RECIPIENTS_ONLY",
"allowedPartiesForDistributingContent": "ELIGIBLE_INTERNAL_USERS",
},
}
},
{
"setting": {
"type": "settings/drive_and_docs.shared_drive_creation",
"value": {
"allowSharedDriveCreation": True,
"allowManagersToOverrideSettings": False,
"allowNonMemberAccess": False,
"allowedPartiesForDownloadPrintCopy": "EDITORS_ONLY",
},
}
},
{
"setting": {
"type": "settings/drive_and_docs.drive_for_desktop",
"value": {"allowDriveForDesktop": False},
}
},
]
}
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.drive.drive_service.GoogleWorkspaceService._build_service",
return_value=mock_service,
),
):
from prowler.providers.googleworkspace.services.drive.drive_service import (
Drive,
)
drive = Drive(mock_provider)
assert drive.policies_fetched is True
assert drive.policies.external_sharing_mode == "ALLOWLISTED_DOMAINS"
assert drive.policies.warn_for_external_sharing is True
assert drive.policies.warn_for_sharing_outside_allowlisted_domains is True
assert drive.policies.allow_publishing_files is False
assert drive.policies.access_checker_suggestions == "RECIPIENTS_ONLY"
assert (
drive.policies.allowed_parties_for_distributing_content
== "ELIGIBLE_INTERNAL_USERS"
)
assert drive.policies.allow_shared_drive_creation is True
assert drive.policies.allow_managers_to_override_settings is False
assert drive.policies.allow_non_member_access is False
assert (
drive.policies.allowed_parties_for_download_print_copy == "EDITORS_ONLY"
)
assert drive.policies.allow_drive_for_desktop is False
def test_drive_fetch_policies_empty_response(self):
"""Test handling empty policies response"""
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.drive.drive_service.GoogleWorkspaceService._build_service",
return_value=mock_service,
),
):
from prowler.providers.googleworkspace.services.drive.drive_service import (
Drive,
)
drive = Drive(mock_provider)
assert drive.policies_fetched is True
assert drive.policies.external_sharing_mode is None
assert drive.policies.warn_for_external_sharing is None
assert drive.policies.allow_publishing_files is None
assert drive.policies.allow_shared_drive_creation is None
assert drive.policies.allow_drive_for_desktop is None
def test_drive_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.drive.drive_service.GoogleWorkspaceService._build_service",
return_value=mock_service,
),
):
from prowler.providers.googleworkspace.services.drive.drive_service import (
Drive,
)
drive = Drive(mock_provider)
assert drive.policies_fetched is False
assert drive.policies.external_sharing_mode is None
assert drive.policies.allow_shared_drive_creation is None
assert drive.policies.allow_drive_for_desktop is None
def test_drive_fetch_policies_build_service_returns_none(self):
"""Test early return when _build_service fails to construct the client"""
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.drive.drive_service.GoogleWorkspaceService._build_service",
return_value=None,
),
):
from prowler.providers.googleworkspace.services.drive.drive_service import (
Drive,
)
drive = Drive(mock_provider)
assert drive.policies_fetched is False
assert drive.policies.external_sharing_mode is None
assert drive.policies.allow_shared_drive_creation is None
assert drive.policies.allow_drive_for_desktop is None
def test_drive_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.drive.drive_service.GoogleWorkspaceService._build_service",
return_value=mock_service,
),
):
from prowler.providers.googleworkspace.services.drive.drive_service import (
Drive,
)
drive = Drive(mock_provider)
assert drive.policies_fetched is False
assert drive.policies.external_sharing_mode is None
assert drive.policies.allow_shared_drive_creation is None
assert drive.policies.allow_drive_for_desktop is None
def test_drive_policies_model(self):
"""Test DrivePolicies Pydantic model"""
from prowler.providers.googleworkspace.services.drive.drive_service import (
DrivePolicies,
)
policies = DrivePolicies(
external_sharing_mode="ALLOWLISTED_DOMAINS",
warn_for_external_sharing=True,
warn_for_sharing_outside_allowlisted_domains=True,
allow_publishing_files=False,
access_checker_suggestions="RECIPIENTS_ONLY",
allowed_parties_for_distributing_content="ELIGIBLE_INTERNAL_USERS",
allow_shared_drive_creation=True,
allow_managers_to_override_settings=False,
allow_non_member_access=False,
allowed_parties_for_download_print_copy="EDITORS_ONLY",
allow_drive_for_desktop=False,
)
assert policies.external_sharing_mode == "ALLOWLISTED_DOMAINS"
assert policies.warn_for_external_sharing is True
assert policies.allow_publishing_files is False
assert policies.access_checker_suggestions == "RECIPIENTS_ONLY"
assert policies.allow_shared_drive_creation is True
assert policies.allow_managers_to_override_settings is False
assert policies.allow_non_member_access is False
assert policies.allowed_parties_for_download_print_copy == "EDITORS_ONLY"
assert policies.allow_drive_for_desktop is False
def test_drive_fetch_policies_ignores_ou_and_group_level(self):
"""Test that OU-level and group-level policies are skipped, only customer-level used"""
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()
# Response includes 3 policies of the same type at different org levels:
# customer-level (no policyQuery), OU-level, and group-level
mock_policies_list.execute.return_value = {
"policies": [
{
# Customer-level: no policyQuery → should be used
"setting": {
"type": "settings/drive_and_docs.external_sharing",
"value": {
"externalSharingMode": "ALLOWLISTED_DOMAINS",
"warnForExternalSharing": True,
},
}
},
{
# OU-level: has policyQuery.orgUnit → should be skipped
"policyQuery": {"orgUnit": "orgUnits/sales_team"},
"setting": {
"type": "settings/drive_and_docs.external_sharing",
"value": {
"externalSharingMode": "ALLOWED",
"warnForExternalSharing": False,
},
},
},
{
# Group-level: has policyQuery.group → should be skipped
"policyQuery": {"group": "groups/contractors"},
"setting": {
"type": "settings/drive_and_docs.external_sharing",
"value": {
"externalSharingMode": "DISALLOWED",
"warnForExternalSharing": False,
},
},
},
{
# Customer-level shared drive creation
"setting": {
"type": "settings/drive_and_docs.shared_drive_creation",
"value": {"allowSharedDriveCreation": True},
}
},
{
# OU-level shared drive creation → should be skipped
"policyQuery": {"orgUnit": "orgUnits/engineering"},
"setting": {
"type": "settings/drive_and_docs.shared_drive_creation",
"value": {"allowSharedDriveCreation": False},
},
},
]
}
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.drive.drive_service.GoogleWorkspaceService._build_service",
return_value=mock_service,
),
):
from prowler.providers.googleworkspace.services.drive.drive_service import (
Drive,
)
drive = Drive(mock_provider)
assert drive.policies_fetched is True
# Customer-level values should be stored
assert drive.policies.external_sharing_mode == "ALLOWLISTED_DOMAINS"
assert drive.policies.warn_for_external_sharing is True
assert drive.policies.allow_shared_drive_creation is True
# OU/group-level values (ALLOWED, False, False) should NOT have overwritten
@@ -0,0 +1,124 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.drive.drive_service import DrivePolicies
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
DOMAIN,
set_mocked_googleworkspace_provider,
)
class TestDriveSharedDriveCreationAllowed:
def test_pass_creation_allowed(self):
"""Test PASS when users are allowed to create shared drives"""
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.drive.drive_shared_drive_creation_allowed.drive_shared_drive_creation_allowed.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_shared_drive_creation_allowed.drive_shared_drive_creation_allowed import (
drive_shared_drive_creation_allowed,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(allow_shared_drive_creation=True)
check = drive_shared_drive_creation_allowed()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "allowed" in findings[0].status_extended
assert findings[0].resource_name == DOMAIN
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_creation_disabled(self):
"""Test FAIL when users are prevented from creating shared drives"""
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.drive.drive_shared_drive_creation_allowed.drive_shared_drive_creation_allowed.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_shared_drive_creation_allowed.drive_shared_drive_creation_allowed import (
drive_shared_drive_creation_allowed,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(
allow_shared_drive_creation=False
)
check = drive_shared_drive_creation_allowed()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "prevented" in findings[0].status_extended
def test_fail_no_policy_set(self):
"""Test FAIL when no explicit policy is set (None) but fetch succeeded"""
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.drive.drive_shared_drive_creation_allowed.drive_shared_drive_creation_allowed.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_shared_drive_creation_allowed.drive_shared_drive_creation_allowed import (
drive_shared_drive_creation_allowed,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(allow_shared_drive_creation=None)
check = drive_shared_drive_creation_allowed()
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.drive.drive_shared_drive_creation_allowed.drive_shared_drive_creation_allowed.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_shared_drive_creation_allowed.drive_shared_drive_creation_allowed import (
drive_shared_drive_creation_allowed,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = False
mock_drive_client.policies = DrivePolicies()
check = drive_shared_drive_creation_allowed()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,158 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.drive.drive_service import DrivePolicies
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
DOMAIN,
set_mocked_googleworkspace_provider,
)
class TestDriveSharedDriveDisableDownloadPrintCopy:
def test_pass_editors_only(self):
"""Test PASS when download/print/copy is restricted to editors 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.drive.drive_shared_drive_disable_download_print_copy.drive_shared_drive_disable_download_print_copy.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_shared_drive_disable_download_print_copy.drive_shared_drive_disable_download_print_copy import (
drive_shared_drive_disable_download_print_copy,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(
allowed_parties_for_download_print_copy="EDITORS_ONLY"
)
check = drive_shared_drive_disable_download_print_copy()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "EDITORS_ONLY" in findings[0].status_extended
assert findings[0].resource_name == DOMAIN
assert findings[0].customer_id == CUSTOMER_ID
def test_pass_managers_only(self):
"""Test PASS when download/print/copy is restricted to managers 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.drive.drive_shared_drive_disable_download_print_copy.drive_shared_drive_disable_download_print_copy.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_shared_drive_disable_download_print_copy.drive_shared_drive_disable_download_print_copy import (
drive_shared_drive_disable_download_print_copy,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(
allowed_parties_for_download_print_copy="MANAGERS_ONLY"
)
check = drive_shared_drive_disable_download_print_copy()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "MANAGERS_ONLY" in findings[0].status_extended
def test_fail_all_allowed(self):
"""Test FAIL when all users (including viewers/commenters) can download/print/copy"""
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.drive.drive_shared_drive_disable_download_print_copy.drive_shared_drive_disable_download_print_copy.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_shared_drive_disable_download_print_copy.drive_shared_drive_disable_download_print_copy import (
drive_shared_drive_disable_download_print_copy,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(
allowed_parties_for_download_print_copy="ALL"
)
check = drive_shared_drive_disable_download_print_copy()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "ALL" in findings[0].status_extended
def test_fail_no_policy_set(self):
"""Test FAIL when no explicit policy is set (None) but fetch succeeded"""
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.drive.drive_shared_drive_disable_download_print_copy.drive_shared_drive_disable_download_print_copy.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_shared_drive_disable_download_print_copy.drive_shared_drive_disable_download_print_copy import (
drive_shared_drive_disable_download_print_copy,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(
allowed_parties_for_download_print_copy=None
)
check = drive_shared_drive_disable_download_print_copy()
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.drive.drive_shared_drive_disable_download_print_copy.drive_shared_drive_disable_download_print_copy.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_shared_drive_disable_download_print_copy.drive_shared_drive_disable_download_print_copy import (
drive_shared_drive_disable_download_print_copy,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = False
mock_drive_client.policies = DrivePolicies()
check = drive_shared_drive_disable_download_print_copy()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,128 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.drive.drive_service import DrivePolicies
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
DOMAIN,
set_mocked_googleworkspace_provider,
)
class TestDriveSharedDriveManagersCannotOverride:
def test_pass_override_disabled(self):
"""Test PASS when managers cannot override shared drive settings"""
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.drive.drive_shared_drive_managers_cannot_override.drive_shared_drive_managers_cannot_override.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_shared_drive_managers_cannot_override.drive_shared_drive_managers_cannot_override import (
drive_shared_drive_managers_cannot_override,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(
allow_managers_to_override_settings=False
)
check = drive_shared_drive_managers_cannot_override()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "cannot override" in findings[0].status_extended
assert findings[0].resource_name == DOMAIN
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_override_allowed(self):
"""Test FAIL when managers can override shared drive settings"""
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.drive.drive_shared_drive_managers_cannot_override.drive_shared_drive_managers_cannot_override.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_shared_drive_managers_cannot_override.drive_shared_drive_managers_cannot_override import (
drive_shared_drive_managers_cannot_override,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(
allow_managers_to_override_settings=True
)
check = drive_shared_drive_managers_cannot_override()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "allowed to override" in findings[0].status_extended
def test_fail_no_policy_set(self):
"""Test FAIL when no explicit policy is set (None) but fetch succeeded"""
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.drive.drive_shared_drive_managers_cannot_override.drive_shared_drive_managers_cannot_override.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_shared_drive_managers_cannot_override.drive_shared_drive_managers_cannot_override import (
drive_shared_drive_managers_cannot_override,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(
allow_managers_to_override_settings=None
)
check = drive_shared_drive_managers_cannot_override()
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.drive.drive_shared_drive_managers_cannot_override.drive_shared_drive_managers_cannot_override.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_shared_drive_managers_cannot_override.drive_shared_drive_managers_cannot_override import (
drive_shared_drive_managers_cannot_override,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = False
mock_drive_client.policies = DrivePolicies()
check = drive_shared_drive_managers_cannot_override()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,122 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.drive.drive_service import DrivePolicies
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
DOMAIN,
set_mocked_googleworkspace_provider,
)
class TestDriveSharedDriveMembersOnlyAccess:
def test_pass_non_member_access_disabled(self):
"""Test PASS when non-member access to shared drive files 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.drive.drive_shared_drive_members_only_access.drive_shared_drive_members_only_access.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_shared_drive_members_only_access.drive_shared_drive_members_only_access import (
drive_shared_drive_members_only_access,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(allow_non_member_access=False)
check = drive_shared_drive_members_only_access()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "members only" in findings[0].status_extended
assert findings[0].resource_name == DOMAIN
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_non_member_access_enabled(self):
"""Test FAIL when non-members can be added to shared drive files"""
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.drive.drive_shared_drive_members_only_access.drive_shared_drive_members_only_access.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_shared_drive_members_only_access.drive_shared_drive_members_only_access import (
drive_shared_drive_members_only_access,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(allow_non_member_access=True)
check = drive_shared_drive_members_only_access()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "non-members" in findings[0].status_extended
def test_fail_no_policy_set(self):
"""Test FAIL when no explicit policy is set (None) but fetch succeeded"""
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.drive.drive_shared_drive_members_only_access.drive_shared_drive_members_only_access.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_shared_drive_members_only_access.drive_shared_drive_members_only_access import (
drive_shared_drive_members_only_access,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(allow_non_member_access=None)
check = drive_shared_drive_members_only_access()
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.drive.drive_shared_drive_members_only_access.drive_shared_drive_members_only_access.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_shared_drive_members_only_access.drive_shared_drive_members_only_access import (
drive_shared_drive_members_only_access,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = False
mock_drive_client.policies = DrivePolicies()
check = drive_shared_drive_members_only_access()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,154 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.drive.drive_service import DrivePolicies
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
DOMAIN,
set_mocked_googleworkspace_provider,
)
class TestDriveSharingAllowlistedDomains:
def test_pass_allowlisted_domains(self):
"""Test PASS when external sharing is restricted to allowlisted domains"""
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.drive.drive_sharing_allowlisted_domains.drive_sharing_allowlisted_domains.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_sharing_allowlisted_domains.drive_sharing_allowlisted_domains import (
drive_sharing_allowlisted_domains,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(
external_sharing_mode="ALLOWLISTED_DOMAINS"
)
check = drive_sharing_allowlisted_domains()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "allowlisted domains" in findings[0].status_extended
assert findings[0].resource_name == DOMAIN
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_allowed(self):
"""Test FAIL when external sharing is set to ALLOWED (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.drive.drive_sharing_allowlisted_domains.drive_sharing_allowlisted_domains.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_sharing_allowlisted_domains.drive_sharing_allowlisted_domains import (
drive_sharing_allowlisted_domains,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(external_sharing_mode="ALLOWED")
check = drive_sharing_allowlisted_domains()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "ALLOWED" in findings[0].status_extended
def test_fail_disallowed(self):
"""Test FAIL when external sharing is DISALLOWED (stricter than allowlist but not the expected value)"""
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.drive.drive_sharing_allowlisted_domains.drive_sharing_allowlisted_domains.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_sharing_allowlisted_domains.drive_sharing_allowlisted_domains import (
drive_sharing_allowlisted_domains,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(
external_sharing_mode="DISALLOWED"
)
check = drive_sharing_allowlisted_domains()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "DISALLOWED" in findings[0].status_extended
def test_fail_no_policy_set(self):
"""Test FAIL when no explicit policy is set (None) but fetch succeeded"""
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.drive.drive_sharing_allowlisted_domains.drive_sharing_allowlisted_domains.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_sharing_allowlisted_domains.drive_sharing_allowlisted_domains import (
drive_sharing_allowlisted_domains,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(external_sharing_mode=None)
check = drive_sharing_allowlisted_domains()
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.drive.drive_sharing_allowlisted_domains.drive_sharing_allowlisted_domains.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_sharing_allowlisted_domains.drive_sharing_allowlisted_domains import (
drive_sharing_allowlisted_domains,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = False
mock_drive_client.policies = DrivePolicies()
check = drive_sharing_allowlisted_domains()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,128 @@
from unittest.mock import patch
from prowler.providers.googleworkspace.services.drive.drive_service import DrivePolicies
from tests.providers.googleworkspace.googleworkspace_fixtures import (
CUSTOMER_ID,
DOMAIN,
set_mocked_googleworkspace_provider,
)
class TestDriveWarnSharingWithAllowlistedDomains:
def test_pass_warning_enabled(self):
"""Test PASS when warning for sharing with allowlisted domains 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.drive.drive_warn_sharing_with_allowlisted_domains.drive_warn_sharing_with_allowlisted_domains.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_warn_sharing_with_allowlisted_domains.drive_warn_sharing_with_allowlisted_domains import (
drive_warn_sharing_with_allowlisted_domains,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(
warn_for_sharing_outside_allowlisted_domains=True
)
check = drive_warn_sharing_with_allowlisted_domains()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "PASS"
assert "warned" in findings[0].status_extended
assert findings[0].resource_name == DOMAIN
assert findings[0].customer_id == CUSTOMER_ID
def test_fail_warning_disabled(self):
"""Test FAIL when warning for sharing with allowlisted domains 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.drive.drive_warn_sharing_with_allowlisted_domains.drive_warn_sharing_with_allowlisted_domains.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_warn_sharing_with_allowlisted_domains.drive_warn_sharing_with_allowlisted_domains import (
drive_warn_sharing_with_allowlisted_domains,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(
warn_for_sharing_outside_allowlisted_domains=False
)
check = drive_warn_sharing_with_allowlisted_domains()
findings = check.execute()
assert len(findings) == 1
assert findings[0].status == "FAIL"
assert "disabled" in findings[0].status_extended
def test_fail_no_policy_set(self):
"""Test FAIL when no explicit policy is set (None) but fetch succeeded"""
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.drive.drive_warn_sharing_with_allowlisted_domains.drive_warn_sharing_with_allowlisted_domains.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_warn_sharing_with_allowlisted_domains.drive_warn_sharing_with_allowlisted_domains import (
drive_warn_sharing_with_allowlisted_domains,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = True
mock_drive_client.policies = DrivePolicies(
warn_for_sharing_outside_allowlisted_domains=None
)
check = drive_warn_sharing_with_allowlisted_domains()
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.drive.drive_warn_sharing_with_allowlisted_domains.drive_warn_sharing_with_allowlisted_domains.drive_client"
) as mock_drive_client,
):
from prowler.providers.googleworkspace.services.drive.drive_warn_sharing_with_allowlisted_domains.drive_warn_sharing_with_allowlisted_domains import (
drive_warn_sharing_with_allowlisted_domains,
)
mock_drive_client.provider = mock_provider
mock_drive_client.policies_fetched = False
mock_drive_client.policies = DrivePolicies()
check = drive_warn_sharing_with_allowlisted_domains()
findings = check.execute()
assert len(findings) == 0
@@ -0,0 +1,858 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.m365.services.entra.entra_service import (
ApplicationEnforcedRestrictions,
ApplicationsConditions,
ConditionalAccessGrantControl,
ConditionalAccessPolicyState,
Conditions,
GrantControlOperator,
GrantControls,
PersistentBrowser,
SessionControls,
SignInFrequency,
SignInFrequencyInterval,
UsersConditions,
)
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
CHECK_MODULE_PATH = "prowler.providers.m365.services.entra.entra_conditional_access_policy_all_apps_all_users.entra_conditional_access_policy_all_apps_all_users"
def _make_session_controls():
"""Return default session controls for test policies."""
return SessionControls(
persistent_browser=PersistentBrowser(is_enabled=False, mode="always"),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.EVERY_TIME,
),
application_enforced_restrictions=ApplicationEnforcedRestrictions(
is_enabled=False
),
)
def _make_conditions(
included_applications=None,
included_users=None,
excluded_applications=None,
excluded_users=None,
excluded_groups=None,
excluded_roles=None,
):
"""Return Conditions with the given application and user scopes."""
return Conditions(
application_conditions=ApplicationsConditions(
included_applications=included_applications or ["All"],
excluded_applications=excluded_applications or [],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=excluded_groups or [],
included_users=included_users or ["All"],
excluded_users=excluded_users or [],
included_roles=[],
excluded_roles=excluded_roles or [],
),
client_app_types=[],
user_risk_levels=[],
)
def _make_grant_controls(built_in_controls=None):
"""Return GrantControls with the given built-in controls."""
return GrantControls(
built_in_controls=built_in_controls or [ConditionalAccessGrantControl.MFA],
operator=GrantControlOperator.AND,
authentication_strength=None,
)
class Test_entra_conditional_access_policy_all_apps_all_users:
def test_no_conditional_access_policies(self):
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_all_apps_all_users.entra_conditional_access_policy_all_apps_all_users import (
entra_conditional_access_policy_all_apps_all_users,
)
entra_client.conditional_access_policies = {}
check = entra_conditional_access_policy_all_apps_all_users()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy covers all cloud apps and all users."
)
assert result[0].resource == {}
assert result[0].resource_name == "Conditional Access Policies"
assert result[0].resource_id == "conditionalAccessPolicies"
assert result[0].location == "global"
def test_policy_disabled(self):
policy_id = str(uuid4())
display_name = "All Apps All Users Policy"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_all_apps_all_users.entra_conditional_access_policy_all_apps_all_users import (
entra_conditional_access_policy_all_apps_all_users,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=_make_conditions(),
grant_controls=_make_grant_controls(),
session_controls=_make_session_controls(),
state=ConditionalAccessPolicyState.DISABLED,
)
}
check = entra_conditional_access_policy_all_apps_all_users()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy covers all cloud apps and all users."
)
assert result[0].resource == {}
assert result[0].resource_name == "Conditional Access Policies"
assert result[0].resource_id == "conditionalAccessPolicies"
assert result[0].location == "global"
def test_policy_excluding_roles(self):
policy_id = str(uuid4())
display_name = "All Users Except Role Policy"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_all_apps_all_users.entra_conditional_access_policy_all_apps_all_users import (
entra_conditional_access_policy_all_apps_all_users,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=_make_conditions(
excluded_roles=["62e90394-69f5-4237-9190-012177145e10"],
),
grant_controls=_make_grant_controls(),
session_controls=_make_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_all_apps_all_users()
result = check.execute()
assert len(result) == 1
assert result[0].status == "MANUAL"
assert (
result[0].status_extended
== f"Conditional Access Policy {display_name} targets all cloud apps and all users but includes exclusions. Review excluded users/groups/roles/applications and verify compensating policies protect excluded identities and apps."
)
assert (
result[0].resource
== entra_client.conditional_access_policies[policy_id].dict()
)
assert result[0].resource_name == display_name
assert result[0].resource_id == policy_id
assert result[0].location == "global"
def test_policy_not_targeting_all_apps(self):
policy_id = str(uuid4())
display_name = "Specific App Policy"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_all_apps_all_users.entra_conditional_access_policy_all_apps_all_users import (
entra_conditional_access_policy_all_apps_all_users,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=_make_conditions(
included_applications=["some-app-id"],
),
grant_controls=_make_grant_controls(),
session_controls=_make_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_all_apps_all_users()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy covers all cloud apps and all users."
)
assert result[0].resource == {}
assert result[0].resource_name == "Conditional Access Policies"
assert result[0].resource_id == "conditionalAccessPolicies"
assert result[0].location == "global"
def test_policy_not_targeting_all_users(self):
policy_id = str(uuid4())
display_name = "Specific Users Policy"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_all_apps_all_users.entra_conditional_access_policy_all_apps_all_users import (
entra_conditional_access_policy_all_apps_all_users,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=_make_conditions(
included_users=["some-user-id"],
),
grant_controls=_make_grant_controls(),
session_controls=_make_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_all_apps_all_users()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy covers all cloud apps and all users."
)
assert result[0].resource == {}
assert result[0].resource_name == "Conditional Access Policies"
assert result[0].resource_id == "conditionalAccessPolicies"
assert result[0].location == "global"
def test_policy_excluding_applications(self):
policy_id = str(uuid4())
display_name = "All Apps Except One Policy"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_all_apps_all_users.entra_conditional_access_policy_all_apps_all_users import (
entra_conditional_access_policy_all_apps_all_users,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=_make_conditions(
excluded_applications=["excluded-app-id"],
),
grant_controls=_make_grant_controls(),
session_controls=_make_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_all_apps_all_users()
result = check.execute()
assert len(result) == 1
assert result[0].status == "MANUAL"
assert (
result[0].status_extended
== f"Conditional Access Policy {display_name} targets all cloud apps and all users but includes exclusions. Review excluded users/groups/roles/applications and verify compensating policies protect excluded identities and apps."
)
assert (
result[0].resource
== entra_client.conditional_access_policies[policy_id].dict()
)
assert result[0].resource_name == display_name
assert result[0].resource_id == policy_id
assert result[0].location == "global"
def test_policy_excluding_users(self):
policy_id = str(uuid4())
display_name = "All Users Except One Policy"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_all_apps_all_users.entra_conditional_access_policy_all_apps_all_users import (
entra_conditional_access_policy_all_apps_all_users,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=_make_conditions(
excluded_users=["excluded-user-id"],
),
grant_controls=_make_grant_controls(),
session_controls=_make_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_all_apps_all_users()
result = check.execute()
assert len(result) == 1
assert result[0].status == "MANUAL"
assert (
result[0].status_extended
== f"Conditional Access Policy {display_name} targets all cloud apps and all users but includes exclusions. Review excluded users/groups/roles/applications and verify compensating policies protect excluded identities and apps."
)
assert (
result[0].resource
== entra_client.conditional_access_policies[policy_id].dict()
)
assert result[0].resource_name == display_name
assert result[0].resource_id == policy_id
assert result[0].location == "global"
def test_policy_only_password_change(self):
policy_id = str(uuid4())
display_name = "Password Change Only Policy"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_all_apps_all_users.entra_conditional_access_policy_all_apps_all_users import (
entra_conditional_access_policy_all_apps_all_users,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=_make_conditions(),
grant_controls=_make_grant_controls(
built_in_controls=[
ConditionalAccessGrantControl.PASSWORD_CHANGE
],
),
session_controls=_make_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_all_apps_all_users()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy covers all cloud apps and all users."
)
assert result[0].resource == {}
assert result[0].resource_name == "Conditional Access Policies"
assert result[0].resource_id == "conditionalAccessPolicies"
assert result[0].location == "global"
def test_policy_enabled_for_reporting(self):
policy_id = str(uuid4())
display_name = "All Apps All Users - Report Only"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_all_apps_all_users.entra_conditional_access_policy_all_apps_all_users import (
entra_conditional_access_policy_all_apps_all_users,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=_make_conditions(),
grant_controls=_make_grant_controls(),
session_controls=_make_session_controls(),
state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING,
)
}
check = entra_conditional_access_policy_all_apps_all_users()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Conditional Access Policy {display_name} covers all cloud apps and all users but is only in report-only mode."
)
assert (
result[0].resource
== entra_client.conditional_access_policies[policy_id].dict()
)
assert result[0].resource_name == display_name
assert result[0].resource_id == policy_id
assert result[0].location == "global"
def test_policy_enabled_all_apps_all_users(self):
policy_id = str(uuid4())
display_name = "All Apps All Users Policy"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_all_apps_all_users.entra_conditional_access_policy_all_apps_all_users import (
entra_conditional_access_policy_all_apps_all_users,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=_make_conditions(),
grant_controls=_make_grant_controls(),
session_controls=_make_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_all_apps_all_users()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Conditional Access Policy {display_name} covers all cloud apps and all users."
)
assert (
result[0].resource
== entra_client.conditional_access_policies[policy_id].dict()
)
assert result[0].resource_name == display_name
assert result[0].resource_id == policy_id
assert result[0].location == "global"
def test_multiple_policies_first_disabled_second_enabled(self):
disabled_id = str(uuid4())
enabled_id = str(uuid4())
enabled_name = "All Apps All Users - Enabled"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_all_apps_all_users.entra_conditional_access_policy_all_apps_all_users import (
entra_conditional_access_policy_all_apps_all_users,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
disabled_id: ConditionalAccessPolicy(
id=disabled_id,
display_name="All Apps All Users - Disabled",
conditions=_make_conditions(),
grant_controls=_make_grant_controls(),
session_controls=_make_session_controls(),
state=ConditionalAccessPolicyState.DISABLED,
),
enabled_id: ConditionalAccessPolicy(
id=enabled_id,
display_name=enabled_name,
conditions=_make_conditions(),
grant_controls=_make_grant_controls(),
session_controls=_make_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
),
}
check = entra_conditional_access_policy_all_apps_all_users()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Conditional Access Policy {enabled_name} covers all cloud apps and all users."
)
assert (
result[0].resource
== entra_client.conditional_access_policies[enabled_id].dict()
)
assert result[0].resource_name == enabled_name
assert result[0].resource_id == enabled_id
assert result[0].location == "global"
def test_policy_with_block_grant_control(self):
policy_id = str(uuid4())
display_name = "Block All Apps All Users"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_all_apps_all_users.entra_conditional_access_policy_all_apps_all_users import (
entra_conditional_access_policy_all_apps_all_users,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=_make_conditions(),
grant_controls=_make_grant_controls(
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
),
session_controls=_make_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_all_apps_all_users()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Conditional Access Policy {display_name} covers all cloud apps and all users."
)
assert result[0].resource_name == display_name
assert result[0].resource_id == policy_id
assert result[0].location == "global"
def test_manual_policy_does_not_prevent_later_pass(self):
manual_policy_id = str(uuid4())
pass_policy_id = str(uuid4())
manual_display_name = "All Apps All Users With Exclusions"
pass_display_name = "All Apps All Users Enforced"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_all_apps_all_users.entra_conditional_access_policy_all_apps_all_users import (
entra_conditional_access_policy_all_apps_all_users,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
manual_policy_id: ConditionalAccessPolicy(
id=manual_policy_id,
display_name=manual_display_name,
conditions=_make_conditions(
excluded_users=["excluded-user-id"],
),
grant_controls=_make_grant_controls(),
session_controls=_make_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
),
pass_policy_id: ConditionalAccessPolicy(
id=pass_policy_id,
display_name=pass_display_name,
conditions=_make_conditions(),
grant_controls=_make_grant_controls(),
session_controls=_make_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
),
}
check = entra_conditional_access_policy_all_apps_all_users()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Conditional Access Policy {pass_display_name} covers all cloud apps and all users."
)
assert (
result[0].resource
== entra_client.conditional_access_policies[pass_policy_id].dict()
)
assert result[0].resource_name == pass_display_name
assert result[0].resource_id == pass_policy_id
def test_policy_no_application_conditions(self):
policy_id = str(uuid4())
display_name = "No App Conditions Policy"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_all_apps_all_users.entra_conditional_access_policy_all_apps_all_users import (
entra_conditional_access_policy_all_apps_all_users,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=Conditions(
application_conditions=None,
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
),
grant_controls=_make_grant_controls(),
session_controls=_make_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_all_apps_all_users()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy covers all cloud apps and all users."
)
assert result[0].resource == {}
assert result[0].resource_name == "Conditional Access Policies"
assert result[0].resource_id == "conditionalAccessPolicies"
assert result[0].location == "global"
def test_policy_no_user_conditions(self):
policy_id = str(uuid4())
display_name = "No User Conditions Policy"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_all_apps_all_users.entra_conditional_access_policy_all_apps_all_users import (
entra_conditional_access_policy_all_apps_all_users,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=None,
client_app_types=[],
user_risk_levels=[],
),
grant_controls=_make_grant_controls(),
session_controls=_make_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_all_apps_all_users()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy covers all cloud apps and all users."
)
assert result[0].resource == {}
assert result[0].resource_name == "Conditional Access Policies"
assert result[0].resource_id == "conditionalAccessPolicies"
assert result[0].location == "global"
@@ -222,7 +222,7 @@ class Test_entra_conditional_access_policy_approved_client_app_required_for_mobi
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Conditional Access Policy '{display_name}' reports the requirement of approved client apps or app protection for mobile devices but does not enforce it."
== f"Conditional Access Policy {display_name} reports the requirement of approved client apps or app protection for mobile devices but does not enforce it."
)
assert (
result[0].resource
@@ -312,7 +312,7 @@ class Test_entra_conditional_access_policy_approved_client_app_required_for_mobi
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Conditional Access Policy '{display_name}' requires approved client apps or app protection for mobile devices."
== f"Conditional Access Policy {display_name} requires approved client apps or app protection for mobile devices."
)
assert (
result[0].resource
@@ -738,7 +738,7 @@ class Test_entra_conditional_access_policy_approved_client_app_required_for_mobi
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Conditional Access Policy '{display_name}' requires approved client apps or app protection for mobile devices."
== f"Conditional Access Policy {display_name} requires approved client apps or app protection for mobile devices."
)
assert (
result[0].resource
@@ -827,7 +827,7 @@ class Test_entra_conditional_access_policy_approved_client_app_required_for_mobi
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Conditional Access Policy '{display_name}' requires approved client apps or app protection for mobile devices."
== f"Conditional Access Policy {display_name} requires approved client apps or app protection for mobile devices."
)
assert (
result[0].resource
@@ -963,7 +963,7 @@ class Test_entra_conditional_access_policy_approved_client_app_required_for_mobi
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Conditional Access Policy '{enabled_name}' requires approved client apps or app protection for mobile devices."
== f"Conditional Access Policy {enabled_name} requires approved client apps or app protection for mobile devices."
)
assert (
result[0].resource
@@ -1052,7 +1052,7 @@ class Test_entra_conditional_access_policy_approved_client_app_required_for_mobi
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Conditional Access Policy '{display_name}' requires approved client apps or app protection for mobile devices."
== f"Conditional Access Policy {display_name} requires approved client apps or app protection for mobile devices."
)
assert (
result[0].resource
@@ -0,0 +1,702 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.m365.services.entra.entra_service import (
ApplicationsConditions,
ConditionalAccessGrantControl,
ConditionalAccessPolicyState,
Conditions,
GrantControlOperator,
GrantControls,
PersistentBrowser,
PlatformConditions,
SessionControls,
SignInFrequency,
SignInFrequencyInterval,
UsersConditions,
)
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
CHECK_MODULE_PATH = "prowler.providers.m365.services.entra.entra_conditional_access_policy_block_unknown_device_platforms.entra_conditional_access_policy_block_unknown_device_platforms"
KNOWN_PLATFORMS = ["android", "iOS", "windows", "macOS", "linux"]
def _make_session_controls() -> SessionControls:
"""Return a minimal SessionControls instance for testing."""
return SessionControls(
persistent_browser=PersistentBrowser(is_enabled=False, mode="always"),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.EVERY_TIME,
),
)
class Test_entra_conditional_access_policy_block_unknown_device_platforms:
"""Tests for the entra_conditional_access_policy_block_unknown_device_platforms check."""
def test_no_conditional_access_policies(self):
"""Test FAIL when there are no Conditional Access policies."""
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_block_unknown_device_platforms.entra_conditional_access_policy_block_unknown_device_platforms import (
entra_conditional_access_policy_block_unknown_device_platforms,
)
entra_client.conditional_access_policies = {}
check = entra_conditional_access_policy_block_unknown_device_platforms()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy blocks access from unknown or unsupported device platforms."
)
assert result[0].resource == {}
assert result[0].resource_name == "Conditional Access Policies"
assert result[0].resource_id == "conditionalAccessPolicies"
assert result[0].location == "global"
def test_policy_disabled(self):
"""Test FAIL when the only matching policy is disabled."""
policy_id = str(uuid4())
display_name = "Block Unknown Platforms"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_block_unknown_device_platforms.entra_conditional_access_policy_block_unknown_device_platforms import (
entra_conditional_access_policy_block_unknown_device_platforms,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
platform_conditions=PlatformConditions(
include_platforms=["all"],
exclude_platforms=KNOWN_PLATFORMS,
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.OR,
authentication_strength=None,
),
session_controls=_make_session_controls(),
state=ConditionalAccessPolicyState.DISABLED,
)
}
check = entra_conditional_access_policy_block_unknown_device_platforms()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy blocks access from unknown or unsupported device platforms."
)
assert result[0].resource == {}
assert result[0].resource_name == "Conditional Access Policies"
assert result[0].resource_id == "conditionalAccessPolicies"
assert result[0].location == "global"
def test_policy_enabled_for_reporting_only(self):
"""Test FAIL when the matching policy is only in report-only mode."""
policy_id = str(uuid4())
display_name = "Block Unknown Platforms"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_block_unknown_device_platforms.entra_conditional_access_policy_block_unknown_device_platforms import (
entra_conditional_access_policy_block_unknown_device_platforms,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
platform_conditions=PlatformConditions(
include_platforms=["all"],
exclude_platforms=KNOWN_PLATFORMS,
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.OR,
authentication_strength=None,
),
session_controls=_make_session_controls(),
state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING,
)
}
check = entra_conditional_access_policy_block_unknown_device_platforms()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "Conditional Access Policy Block Unknown Platforms reports blocking unknown or unsupported device platforms but does not enforce it."
)
assert result[0].resource_name == "Block Unknown Platforms"
assert result[0].resource_id == policy_id
assert result[0].location == "global"
def test_policy_no_platform_conditions(self):
"""Test FAIL when the policy has no platform conditions configured."""
policy_id = str(uuid4())
display_name = "Block Unknown Platforms"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_block_unknown_device_platforms.entra_conditional_access_policy_block_unknown_device_platforms import (
entra_conditional_access_policy_block_unknown_device_platforms,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
platform_conditions=None,
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.OR,
authentication_strength=None,
),
session_controls=_make_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_block_unknown_device_platforms()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy blocks access from unknown or unsupported device platforms."
)
def test_policy_does_not_include_all_platforms(self):
"""Test FAIL when the policy includes specific platforms instead of all."""
policy_id = str(uuid4())
display_name = "Block Specific Platforms"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_block_unknown_device_platforms.entra_conditional_access_policy_block_unknown_device_platforms import (
entra_conditional_access_policy_block_unknown_device_platforms,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
platform_conditions=PlatformConditions(
include_platforms=["android", "iOS"],
exclude_platforms=[],
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.OR,
authentication_strength=None,
),
session_controls=_make_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_block_unknown_device_platforms()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
def test_policy_missing_excluded_known_platforms(self):
"""Test FAIL when the policy includes all platforms but does not exclude all known ones."""
policy_id = str(uuid4())
display_name = "Incomplete Platform Exclusion"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_block_unknown_device_platforms.entra_conditional_access_policy_block_unknown_device_platforms import (
entra_conditional_access_policy_block_unknown_device_platforms,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
# Only exclude 3 of 5 known platforms
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
platform_conditions=PlatformConditions(
include_platforms=["all"],
exclude_platforms=["android", "iOS", "windows"],
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.OR,
authentication_strength=None,
),
session_controls=_make_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_block_unknown_device_platforms()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
def test_policy_no_block_grant_control(self):
"""Test FAIL when the policy has correct platform conditions but does not block."""
policy_id = str(uuid4())
display_name = "MFA Unknown Platforms"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_block_unknown_device_platforms.entra_conditional_access_policy_block_unknown_device_platforms import (
entra_conditional_access_policy_block_unknown_device_platforms,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
platform_conditions=PlatformConditions(
include_platforms=["all"],
exclude_platforms=KNOWN_PLATFORMS,
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
operator=GrantControlOperator.OR,
authentication_strength=None,
),
session_controls=_make_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_block_unknown_device_platforms()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
def test_policy_limited_to_specific_users_and_apps(self):
"""Test PASS when a scoped policy still blocks unknown device platforms."""
policy_id = str(uuid4())
display_name = "Scoped Unknown Platform Block"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_block_unknown_device_platforms.entra_conditional_access_policy_block_unknown_device_platforms import (
entra_conditional_access_policy_block_unknown_device_platforms,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=[
"00000000-0000-0000-0000-000000000001"
],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=["11111111-1111-1111-1111-111111111111"],
excluded_groups=[],
included_users=[],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
platform_conditions=PlatformConditions(
include_platforms=["all"],
exclude_platforms=KNOWN_PLATFORMS,
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.OR,
authentication_strength=None,
),
session_controls=_make_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_block_unknown_device_platforms()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "Conditional Access Policy Scoped Unknown Platform Block blocks access from unknown or unsupported device platforms."
)
def test_policy_enabled_and_compliant(self):
"""Test PASS when an enabled policy blocks unknown device platforms correctly."""
policy_id = str(uuid4())
display_name = "Block Unknown Platforms"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_block_unknown_device_platforms.entra_conditional_access_policy_block_unknown_device_platforms import (
entra_conditional_access_policy_block_unknown_device_platforms,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
platform_conditions=PlatformConditions(
include_platforms=["all"],
exclude_platforms=KNOWN_PLATFORMS,
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.OR,
authentication_strength=None,
),
session_controls=_make_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_block_unknown_device_platforms()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "Conditional Access Policy Block Unknown Platforms blocks access from unknown or unsupported device platforms."
)
assert result[0].resource_name == "Block Unknown Platforms"
assert result[0].resource_id == policy_id
assert result[0].location == "global"
def test_mixed_policies_report_only_and_enabled(self):
"""Test PASS when both report-only and enabled compliant policies exist."""
report_policy_id = str(uuid4())
enabled_policy_id = str(uuid4())
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_block_unknown_device_platforms.entra_conditional_access_policy_block_unknown_device_platforms import (
entra_conditional_access_policy_block_unknown_device_platforms,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
base_conditions = Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
platform_conditions=PlatformConditions(
include_platforms=["all"],
exclude_platforms=KNOWN_PLATFORMS,
),
)
grant_controls = GrantControls(
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.OR,
authentication_strength=None,
)
entra_client.conditional_access_policies = {
report_policy_id: ConditionalAccessPolicy(
id=report_policy_id,
display_name="Report Only Policy",
conditions=base_conditions,
grant_controls=grant_controls,
session_controls=_make_session_controls(),
state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING,
),
enabled_policy_id: ConditionalAccessPolicy(
id=enabled_policy_id,
display_name="Enforced Block Policy",
conditions=base_conditions,
grant_controls=grant_controls,
session_controls=_make_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
),
}
check = entra_conditional_access_policy_block_unknown_device_platforms()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].resource_name == "Enforced Block Policy"
assert result[0].resource_id == enabled_policy_id
@@ -0,0 +1,697 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.m365.services.entra.entra_service import (
ApplicationsConditions,
ConditionalAccessPolicyState,
Conditions,
DeviceConditions,
DeviceFilterMode,
GrantControlOperator,
GrantControls,
PersistentBrowser,
SessionControls,
SignInFrequency,
SignInFrequencyInterval,
SignInFrequencyType,
UsersConditions,
)
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
CHECK_MODULE_PATH = "prowler.providers.m365.services.entra.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced"
def _make_policy(
policy_id,
display_name,
state=ConditionalAccessPolicyState.ENABLED,
included_users=None,
included_applications=None,
sign_in_frequency_enabled=True,
sign_in_frequency_interval=SignInFrequencyInterval.TIME_BASED,
sign_in_frequency_value=1,
sign_in_frequency_type=SignInFrequencyType.HOURS,
device_filter_mode=None,
device_filter_rule=None,
):
"""Create a ConditionalAccessPolicy with the given parameters."""
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
return ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=included_applications or ["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=included_users or ["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
device_conditions=DeviceConditions(
device_filter_mode=device_filter_mode,
device_filter_rule=device_filter_rule,
),
),
grant_controls=GrantControls(
built_in_controls=[],
operator=GrantControlOperator.AND,
authentication_strength=None,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(is_enabled=False, mode="always"),
sign_in_frequency=SignInFrequency(
is_enabled=sign_in_frequency_enabled,
frequency=sign_in_frequency_value,
type=sign_in_frequency_type,
interval=sign_in_frequency_interval,
),
),
state=state,
)
class Test_entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced:
"""Tests for sign-in frequency enforcement on non-corporate devices."""
def test_entra_no_conditional_access_policies(self):
"""Test FAIL when no conditional access policies exist."""
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced import (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced,
)
entra_client.conditional_access_policies = {}
check = (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced()
)
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy enforces sign-in frequency for non-corporate devices."
)
assert result[0].resource == {}
assert result[0].resource_name == "Conditional Access Policies"
assert result[0].resource_id == "conditionalAccessPolicies"
assert result[0].location == "global"
def test_entra_policy_disabled(self):
"""Test FAIL when a qualifying policy is disabled."""
policy_id = str(uuid4())
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced import (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced,
)
entra_client.conditional_access_policies = {
policy_id: _make_policy(
policy_id=policy_id,
display_name="Disabled Policy",
state=ConditionalAccessPolicyState.DISABLED,
device_filter_mode=DeviceFilterMode.INCLUDE,
device_filter_rule='device.isCompliant -ne True -or device.trustType -ne "ServerAD"',
)
}
check = (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced()
)
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].resource_name == "Conditional Access Policies"
assert result[0].resource_id == "conditionalAccessPolicies"
def test_entra_policy_enabled_for_reporting(self):
"""Test FAIL when policy is enabled for reporting but not enforcing."""
policy_id = str(uuid4())
display_name = "Reporting Only Policy"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced import (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced,
)
entra_client.conditional_access_policies = {
policy_id: _make_policy(
policy_id=policy_id,
display_name=display_name,
state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING,
device_filter_mode=DeviceFilterMode.INCLUDE,
device_filter_rule='device.isCompliant -ne True -or device.trustType -ne "ServerAD"',
)
}
check = (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced()
)
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Conditional Access Policy {display_name} reports sign-in frequency for non-corporate devices but does not enforce it."
)
assert result[0].resource_name == display_name
assert result[0].resource_id == policy_id
def test_entra_policy_missing_all_users(self):
"""Test FAIL when policy does not target all users."""
policy_id = str(uuid4())
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced import (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced,
)
entra_client.conditional_access_policies = {
policy_id: _make_policy(
policy_id=policy_id,
display_name="Limited Users Policy",
included_users=["user1@example.com"],
device_filter_mode=DeviceFilterMode.INCLUDE,
device_filter_rule="device.isCompliant -ne True",
)
}
check = (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced()
)
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].resource_name == "Conditional Access Policies"
def test_entra_policy_missing_all_applications(self):
"""Test FAIL when policy does not target all applications."""
policy_id = str(uuid4())
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced import (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced,
)
entra_client.conditional_access_policies = {
policy_id: _make_policy(
policy_id=policy_id,
display_name="Limited Apps Policy",
included_applications=["Office365"],
device_filter_mode=DeviceFilterMode.INCLUDE,
device_filter_rule="device.isCompliant -ne True",
)
}
check = (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced()
)
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].resource_name == "Conditional Access Policies"
def test_entra_policy_sign_in_frequency_not_enabled(self):
"""Test FAIL when sign-in frequency is not enabled."""
policy_id = str(uuid4())
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced import (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced,
)
entra_client.conditional_access_policies = {
policy_id: _make_policy(
policy_id=policy_id,
display_name="No Sign-In Freq Policy",
sign_in_frequency_enabled=False,
device_filter_mode=DeviceFilterMode.INCLUDE,
device_filter_rule="device.isCompliant -ne True",
)
}
check = (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced()
)
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].resource_name == "Conditional Access Policies"
def test_entra_policy_sign_in_frequency_not_time_based(self):
"""Test FAIL when sign-in frequency interval is not time-based."""
policy_id = str(uuid4())
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced import (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced,
)
entra_client.conditional_access_policies = {
policy_id: _make_policy(
policy_id=policy_id,
display_name="EveryTime Policy",
sign_in_frequency_interval=SignInFrequencyInterval.EVERY_TIME,
device_filter_mode=DeviceFilterMode.INCLUDE,
device_filter_rule="device.isCompliant -ne True",
)
}
check = (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced()
)
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].resource_name == "Conditional Access Policies"
def test_entra_policy_no_device_filter(self):
"""Test FAIL when policy has no device filter."""
policy_id = str(uuid4())
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced import (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced,
)
entra_client.conditional_access_policies = {
policy_id: _make_policy(
policy_id=policy_id,
display_name="No Device Filter Policy",
)
}
check = (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced()
)
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].resource_name == "Conditional Access Policies"
def test_entra_policy_device_filter_include_compliant(self):
"""Test PASS with include mode device filter targeting non-compliant devices."""
policy_id = str(uuid4())
display_name = "Sign-In Freq Include Filter"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced import (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced,
)
entra_client.conditional_access_policies = {
policy_id: _make_policy(
policy_id=policy_id,
display_name=display_name,
device_filter_mode=DeviceFilterMode.INCLUDE,
device_filter_rule='device.isCompliant -ne True -or device.trustType -ne "ServerAD"',
)
}
check = (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced()
)
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Conditional Access Policy {display_name} enforces sign-in frequency for non-corporate devices."
)
assert result[0].resource_name == display_name
assert result[0].resource_id == policy_id
def test_entra_policy_device_filter_exclude_compliant(self):
"""Test PASS with exclude mode device filter excluding corporate devices."""
policy_id = str(uuid4())
display_name = "Sign-In Freq Exclude Filter"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced import (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced,
)
entra_client.conditional_access_policies = {
policy_id: _make_policy(
policy_id=policy_id,
display_name=display_name,
device_filter_mode=DeviceFilterMode.EXCLUDE,
device_filter_rule='device.isCompliant -eq True -and device.trustType -eq "ServerAD"',
)
}
check = (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced()
)
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].resource_name == display_name
assert result[0].resource_id == policy_id
def test_entra_policy_device_filter_unrelated_rule(self):
"""Test FAIL when device filter rule does not target corporate device properties."""
policy_id = str(uuid4())
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced import (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced,
)
entra_client.conditional_access_policies = {
policy_id: _make_policy(
policy_id=policy_id,
display_name="Unrelated Filter Policy",
device_filter_mode=DeviceFilterMode.INCLUDE,
device_filter_rule='device.displayName -contains "kiosk"',
)
}
check = (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced()
)
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].resource_name == "Conditional Access Policies"
def test_entra_policy_device_filter_include_corporate_devices(self):
"""Test FAIL when include mode targets only corporate devices."""
policy_id = str(uuid4())
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced import (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced,
)
entra_client.conditional_access_policies = {
policy_id: _make_policy(
policy_id=policy_id,
display_name="Corporate Devices Policy",
device_filter_mode=DeviceFilterMode.INCLUDE,
device_filter_rule="device.isCompliant -eq True",
)
}
check = (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced()
)
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].resource_name == "Conditional Access Policies"
def test_entra_policy_device_filter_exclude_non_corporate_devices(self):
"""Test FAIL when exclude mode excludes non-corporate devices."""
policy_id = str(uuid4())
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced import (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced,
)
entra_client.conditional_access_policies = {
policy_id: _make_policy(
policy_id=policy_id,
display_name="Exclude Unmanaged Devices Policy",
device_filter_mode=DeviceFilterMode.EXCLUDE,
device_filter_rule="device.isCompliant -eq False",
)
}
check = (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced()
)
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].resource_name == "Conditional Access Policies"
def test_entra_multiple_policies_one_compliant(self):
"""Test PASS when at least one policy among multiple is compliant."""
policy_id_1 = str(uuid4())
policy_id_2 = str(uuid4())
display_name_2 = "Compliant Sign-In Freq Policy"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced import (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced,
)
entra_client.conditional_access_policies = {
policy_id_1: _make_policy(
policy_id=policy_id_1,
display_name="Non-Compliant Policy",
sign_in_frequency_enabled=False,
),
policy_id_2: _make_policy(
policy_id=policy_id_2,
display_name=display_name_2,
device_filter_mode=DeviceFilterMode.INCLUDE,
device_filter_rule='device.trustType -ne "ServerAD"',
),
}
check = (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced()
)
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].resource_name == display_name_2
assert result[0].resource_id == policy_id_2
def test_entra_policy_with_trust_type_only(self):
"""Test PASS with device filter referencing only trustType."""
policy_id = str(uuid4())
display_name = "TrustType Filter Policy"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced.entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced import (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced,
)
entra_client.conditional_access_policies = {
policy_id: _make_policy(
policy_id=policy_id,
display_name=display_name,
device_filter_mode=DeviceFilterMode.EXCLUDE,
device_filter_rule='device.trustType -eq "ServerAD"',
)
}
check = (
entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced()
)
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].resource_name == display_name
@@ -0,0 +1,711 @@
"""Tests for the entra_conditional_access_policy_directory_sync_account_excluded check."""
from unittest import mock
from uuid import uuid4
from prowler.providers.m365.services.entra.entra_service import (
ApplicationEnforcedRestrictions,
ApplicationsConditions,
ConditionalAccessGrantControl,
ConditionalAccessPolicyState,
Conditions,
GrantControlOperator,
GrantControls,
PersistentBrowser,
SessionControls,
SignInFrequency,
SignInFrequencyInterval,
UsersConditions,
)
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
DIRECTORY_SYNC_ROLE_TEMPLATE_ID = "d29b2b05-8046-44ba-8758-1e26182fcf32"
CHECK_MODULE_PATH = "prowler.providers.m365.services.entra.entra_conditional_access_policy_directory_sync_account_excluded.entra_conditional_access_policy_directory_sync_account_excluded"
def _default_session_controls():
"""Return default session controls for test policies."""
return SessionControls(
persistent_browser=PersistentBrowser(is_enabled=False, mode="always"),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.EVERY_TIME,
),
application_enforced_restrictions=ApplicationEnforcedRestrictions(
is_enabled=False
),
)
def _default_grant_controls():
"""Return default grant controls requiring MFA for test policies."""
return GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
operator=GrantControlOperator.AND,
authentication_strength=None,
)
class Test_entra_conditional_access_policy_directory_sync_account_excluded:
"""Test class for Directory Sync Account exclusion check."""
def test_no_conditional_access_policies(self):
"""Test PASS when no Conditional Access policies exist."""
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_directory_sync_account_excluded.entra_conditional_access_policy_directory_sync_account_excluded import (
entra_conditional_access_policy_directory_sync_account_excluded,
)
entra_client.conditional_access_policies = {}
check = entra_conditional_access_policy_directory_sync_account_excluded()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "No Conditional Access Policy targets all users and all cloud apps, so no Directory Synchronization Accounts exclusion is needed."
)
assert result[0].resource == {}
assert result[0].resource_name == "Conditional Access Policies"
assert result[0].resource_id == "conditionalAccessPolicies"
assert result[0].location == "global"
def test_policy_disabled(self):
"""Test PASS when only a disabled policy exists targeting all users and apps."""
policy_id = str(uuid4())
display_name = "Require MFA for All Users"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_directory_sync_account_excluded.entra_conditional_access_policy_directory_sync_account_excluded import (
entra_conditional_access_policy_directory_sync_account_excluded,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
),
grant_controls=_default_grant_controls(),
session_controls=_default_session_controls(),
state=ConditionalAccessPolicyState.DISABLED,
)
}
check = entra_conditional_access_policy_directory_sync_account_excluded()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "No Conditional Access Policy targets all users and all cloud apps, so no Directory Synchronization Accounts exclusion is needed."
)
assert result[0].resource == {}
assert result[0].resource_name == "Conditional Access Policies"
assert result[0].resource_id == "conditionalAccessPolicies"
assert result[0].location == "global"
def test_policy_targets_specific_users(self):
"""Test PASS when the policy targets specific users, not all users."""
policy_id = str(uuid4())
display_name = "Require MFA for Admins"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_directory_sync_account_excluded.entra_conditional_access_policy_directory_sync_account_excluded import (
entra_conditional_access_policy_directory_sync_account_excluded,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=["some-group-id"],
excluded_groups=[],
included_users=[],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
),
grant_controls=_default_grant_controls(),
session_controls=_default_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_directory_sync_account_excluded()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "No Conditional Access Policy targets all users and all cloud apps, so no Directory Synchronization Accounts exclusion is needed."
)
def test_policy_targets_specific_apps(self):
"""Test PASS when the policy targets specific apps, not all apps."""
policy_id = str(uuid4())
display_name = "Require MFA for Office 365"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_directory_sync_account_excluded.entra_conditional_access_policy_directory_sync_account_excluded import (
entra_conditional_access_policy_directory_sync_account_excluded,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["some-app-id"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
),
grant_controls=_default_grant_controls(),
session_controls=_default_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_directory_sync_account_excluded()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "No Conditional Access Policy targets all users and all cloud apps, so no Directory Synchronization Accounts exclusion is needed."
)
def test_policy_enabled_without_sync_exclusion(self):
"""Test FAIL when an enabled policy targets all users and all apps but does not exclude the sync role."""
policy_id = str(uuid4())
display_name = "Require MFA for All Users"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_directory_sync_account_excluded.entra_conditional_access_policy_directory_sync_account_excluded import (
entra_conditional_access_policy_directory_sync_account_excluded,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
),
grant_controls=_default_grant_controls(),
session_controls=_default_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_directory_sync_account_excluded()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Conditional Access Policy {display_name} does not exclude the Directory Synchronization Accounts role, which may break Entra Connect sync."
)
assert (
result[0].resource
== entra_client.conditional_access_policies[policy_id].dict()
)
assert result[0].resource_name == display_name
assert result[0].resource_id == policy_id
assert result[0].location == "global"
def test_policy_report_only_without_sync_exclusion(self):
"""Test FAIL when a report-only policy targets all users and apps without excluding the sync role."""
policy_id = str(uuid4())
display_name = "Report Only - Require MFA"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_directory_sync_account_excluded.entra_conditional_access_policy_directory_sync_account_excluded import (
entra_conditional_access_policy_directory_sync_account_excluded,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
),
grant_controls=_default_grant_controls(),
session_controls=_default_session_controls(),
state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING,
)
}
check = entra_conditional_access_policy_directory_sync_account_excluded()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Conditional Access Policy {display_name} reports excluding the Directory Synchronization Accounts role but does not enforce it."
)
assert (
result[0].resource
== entra_client.conditional_access_policies[policy_id].dict()
)
assert result[0].resource_name == display_name
assert result[0].resource_id == policy_id
assert result[0].location == "global"
def test_policy_enabled_with_sync_exclusion(self):
"""Test PASS when an enabled policy targets all users and apps and excludes the sync role."""
policy_id = str(uuid4())
display_name = "Require MFA for All Users"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_directory_sync_account_excluded.entra_conditional_access_policy_directory_sync_account_excluded import (
entra_conditional_access_policy_directory_sync_account_excluded,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[
DIRECTORY_SYNC_ROLE_TEMPLATE_ID,
],
),
client_app_types=[],
user_risk_levels=[],
),
grant_controls=_default_grant_controls(),
session_controls=_default_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_directory_sync_account_excluded()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Conditional Access Policy {display_name} excludes the Directory Synchronization Accounts role."
)
assert (
result[0].resource
== entra_client.conditional_access_policies[policy_id].dict()
)
assert result[0].resource_name == display_name
assert result[0].resource_id == policy_id
assert result[0].location == "global"
def test_policy_with_sync_role_and_other_excluded_roles(self):
"""Test PASS when the sync role is excluded alongside other roles."""
policy_id = str(uuid4())
display_name = "Require MFA for All Users"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_directory_sync_account_excluded.entra_conditional_access_policy_directory_sync_account_excluded import (
entra_conditional_access_policy_directory_sync_account_excluded,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[
"some-other-role-id",
DIRECTORY_SYNC_ROLE_TEMPLATE_ID,
],
),
client_app_types=[],
user_risk_levels=[],
),
grant_controls=_default_grant_controls(),
session_controls=_default_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_directory_sync_account_excluded()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Conditional Access Policy {display_name} excludes the Directory Synchronization Accounts role."
)
def test_multiple_policies_mixed_results(self):
"""Test multiple policies where one excludes sync role and another does not."""
policy_id_pass = str(uuid4())
policy_id_fail = str(uuid4())
display_name_pass = "MFA Policy - With Exclusion"
display_name_fail = "MFA Policy - Without Exclusion"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_directory_sync_account_excluded.entra_conditional_access_policy_directory_sync_account_excluded import (
entra_conditional_access_policy_directory_sync_account_excluded,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id_pass: ConditionalAccessPolicy(
id=policy_id_pass,
display_name=display_name_pass,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[
DIRECTORY_SYNC_ROLE_TEMPLATE_ID,
],
),
client_app_types=[],
user_risk_levels=[],
),
grant_controls=_default_grant_controls(),
session_controls=_default_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
),
policy_id_fail: ConditionalAccessPolicy(
id=policy_id_fail,
display_name=display_name_fail,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
),
grant_controls=_default_grant_controls(),
session_controls=_default_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
),
}
check = entra_conditional_access_policy_directory_sync_account_excluded()
result = check.execute()
assert len(result) == 2
pass_results = [r for r in result if r.status == "PASS"]
fail_results = [r for r in result if r.status == "FAIL"]
assert len(pass_results) == 1
assert len(fail_results) == 1
assert pass_results[0].resource_name == display_name_pass
assert pass_results[0].resource_id == policy_id_pass
assert (
pass_results[0].status_extended
== f"Conditional Access Policy {display_name_pass} excludes the Directory Synchronization Accounts role."
)
assert fail_results[0].resource_name == display_name_fail
assert fail_results[0].resource_id == policy_id_fail
assert (
fail_results[0].status_extended
== f"Conditional Access Policy {display_name_fail} does not exclude the Directory Synchronization Accounts role, which may break Entra Connect sync."
)
def test_policy_with_wrong_excluded_role(self):
"""Test FAIL when the policy excludes a different role but not the sync role."""
policy_id = str(uuid4())
display_name = "Require MFA for All Users"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
f"{CHECK_MODULE_PATH}.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_directory_sync_account_excluded.entra_conditional_access_policy_directory_sync_account_excluded import (
entra_conditional_access_policy_directory_sync_account_excluded,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=["some-other-role-id"],
),
client_app_types=[],
user_risk_levels=[],
),
grant_controls=_default_grant_controls(),
session_controls=_default_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_directory_sync_account_excluded()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Conditional Access Policy {display_name} does not exclude the Directory Synchronization Accounts role, which may break Entra Connect sync."
)
assert result[0].resource_name == display_name
assert result[0].resource_id == policy_id
@@ -0,0 +1,705 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.m365.services.entra.entra_service import (
ALL_GUEST_USER_TYPES,
ApplicationsConditions,
ConditionalAccessGrantControl,
ConditionalAccessPolicyState,
Conditions,
ExternalTenantsMembershipKind,
GrantControlOperator,
GrantControls,
GuestOrExternalUserType,
GuestsOrExternalUsers,
PersistentBrowser,
SessionControls,
SignInFrequency,
UsersConditions,
)
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
CHECK_MODULE_PATH = "prowler.providers.m365.services.entra.entra_conditional_access_policy_mfa_enforced_for_guest_users.entra_conditional_access_policy_mfa_enforced_for_guest_users"
def build_policy(
*,
display_name: str,
state: ConditionalAccessPolicyState,
included_applications: list[str] | None = None,
included_users: list[str] | None = None,
excluded_users: list[str] | None = None,
built_in_controls: list[ConditionalAccessGrantControl] | None = None,
authentication_strength: str | None = None,
included_guests_or_external_users: GuestsOrExternalUsers | None = None,
excluded_guests_or_external_users: GuestsOrExternalUsers | None = None,
):
"""Build a ConditionalAccessPolicy for testing."""
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
return ConditionalAccessPolicy(
id=str(uuid4()),
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=included_applications or ["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=included_users or [],
excluded_users=excluded_users or [],
included_roles=[],
excluded_roles=[],
included_guests_or_external_users=included_guests_or_external_users,
excluded_guests_or_external_users=excluded_guests_or_external_users,
),
client_app_types=[],
user_risk_levels=[],
),
grant_controls=GrantControls(
built_in_controls=built_in_controls or [],
operator=GrantControlOperator.OR,
authentication_strength=authentication_strength,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(is_enabled=False, mode="always"),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=None,
),
),
state=state,
)
class Test_entra_conditional_access_policy_mfa_enforced_for_guest_users:
"""Tests for the entra_conditional_access_policy_mfa_enforced_for_guest_users check."""
def test_no_conditional_access_policies(self):
"""Test FAIL when there are no Conditional Access policies."""
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(f"{CHECK_MODULE_PATH}.entra_client", new=entra_client),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_mfa_enforced_for_guest_users.entra_conditional_access_policy_mfa_enforced_for_guest_users import (
entra_conditional_access_policy_mfa_enforced_for_guest_users,
)
entra_client.conditional_access_policies = {}
result = (
entra_conditional_access_policy_mfa_enforced_for_guest_users().execute()
)
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy requires MFA for guest users."
)
assert result[0].resource == {}
assert result[0].resource_name == "Conditional Access Policies"
assert result[0].resource_id == "conditionalAccessPolicies"
def test_disabled_policy_is_skipped(self):
"""Test FAIL when the only matching policy is disabled."""
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(f"{CHECK_MODULE_PATH}.entra_client", new=entra_client),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_mfa_enforced_for_guest_users.entra_conditional_access_policy_mfa_enforced_for_guest_users import (
entra_conditional_access_policy_mfa_enforced_for_guest_users,
)
policy = build_policy(
display_name="MFA for Guests",
state=ConditionalAccessPolicyState.DISABLED,
included_users=["GuestsOrExternalUsers"],
built_in_controls=[ConditionalAccessGrantControl.MFA],
)
entra_client.conditional_access_policies = {policy.id: policy}
result = (
entra_conditional_access_policy_mfa_enforced_for_guest_users().execute()
)
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy requires MFA for guest users."
)
def test_policy_enabled_targeting_all_users_with_mfa(self):
"""Test PASS when an enabled policy targets all users with MFA for all apps."""
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(f"{CHECK_MODULE_PATH}.entra_client", new=entra_client),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_mfa_enforced_for_guest_users.entra_conditional_access_policy_mfa_enforced_for_guest_users import (
entra_conditional_access_policy_mfa_enforced_for_guest_users,
)
policy = build_policy(
display_name="MFA for All Users",
state=ConditionalAccessPolicyState.ENABLED,
included_users=["All"],
built_in_controls=[ConditionalAccessGrantControl.MFA],
)
entra_client.conditional_access_policies = {policy.id: policy}
result = (
entra_conditional_access_policy_mfa_enforced_for_guest_users().execute()
)
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "Conditional Access Policy MFA for All Users requires MFA for guest users."
)
assert result[0].resource_id == policy.id
assert result[0].resource_name == "MFA for All Users"
def test_policy_enabled_targeting_guests_or_external_users(self):
"""Test PASS when an enabled policy specifically targets GuestsOrExternalUsers."""
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(f"{CHECK_MODULE_PATH}.entra_client", new=entra_client),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_mfa_enforced_for_guest_users.entra_conditional_access_policy_mfa_enforced_for_guest_users import (
entra_conditional_access_policy_mfa_enforced_for_guest_users,
)
policy = build_policy(
display_name="MFA for Guest Users",
state=ConditionalAccessPolicyState.ENABLED,
included_users=["GuestsOrExternalUsers"],
built_in_controls=[ConditionalAccessGrantControl.MFA],
)
entra_client.conditional_access_policies = {policy.id: policy}
result = (
entra_conditional_access_policy_mfa_enforced_for_guest_users().execute()
)
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "Conditional Access Policy MFA for Guest Users requires MFA for guest users."
)
assert result[0].resource_id == policy.id
def test_policy_enabled_targeting_all_guest_types_via_included_guests(self):
"""Test PASS when policy targets all six guest types via included_guests_or_external_users."""
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(f"{CHECK_MODULE_PATH}.entra_client", new=entra_client),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_mfa_enforced_for_guest_users.entra_conditional_access_policy_mfa_enforced_for_guest_users import (
entra_conditional_access_policy_mfa_enforced_for_guest_users,
)
policy = build_policy(
display_name="MFA for All Guest Types",
state=ConditionalAccessPolicyState.ENABLED,
built_in_controls=[ConditionalAccessGrantControl.MFA],
included_guests_or_external_users=GuestsOrExternalUsers(
guest_or_external_user_types=list(ALL_GUEST_USER_TYPES),
),
)
entra_client.conditional_access_policies = {policy.id: policy}
result = (
entra_conditional_access_policy_mfa_enforced_for_guest_users().execute()
)
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].resource_id == policy.id
def test_policy_with_authentication_strength_passes(self):
"""Test PASS when policy uses authentication strength instead of MFA built-in control."""
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(f"{CHECK_MODULE_PATH}.entra_client", new=entra_client),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_mfa_enforced_for_guest_users.entra_conditional_access_policy_mfa_enforced_for_guest_users import (
entra_conditional_access_policy_mfa_enforced_for_guest_users,
)
policy = build_policy(
display_name="Auth Strength for Guests",
state=ConditionalAccessPolicyState.ENABLED,
included_users=["GuestsOrExternalUsers"],
authentication_strength="Phishing-resistant MFA",
)
entra_client.conditional_access_policies = {policy.id: policy}
result = (
entra_conditional_access_policy_mfa_enforced_for_guest_users().execute()
)
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].resource_id == policy.id
def test_policy_only_password_change_fails(self):
"""Test FAIL when the policy only requires password change."""
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(f"{CHECK_MODULE_PATH}.entra_client", new=entra_client),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_mfa_enforced_for_guest_users.entra_conditional_access_policy_mfa_enforced_for_guest_users import (
entra_conditional_access_policy_mfa_enforced_for_guest_users,
)
policy = build_policy(
display_name="Password Change for Guests",
state=ConditionalAccessPolicyState.ENABLED,
included_users=["GuestsOrExternalUsers"],
built_in_controls=[ConditionalAccessGrantControl.PASSWORD_CHANGE],
)
entra_client.conditional_access_policies = {policy.id: policy}
result = (
entra_conditional_access_policy_mfa_enforced_for_guest_users().execute()
)
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy requires MFA for guest users."
)
def test_policy_not_targeting_all_apps_fails(self):
"""Test FAIL when the policy does not target all cloud applications."""
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(f"{CHECK_MODULE_PATH}.entra_client", new=entra_client),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_mfa_enforced_for_guest_users.entra_conditional_access_policy_mfa_enforced_for_guest_users import (
entra_conditional_access_policy_mfa_enforced_for_guest_users,
)
policy = build_policy(
display_name="MFA for Guests Specific App",
state=ConditionalAccessPolicyState.ENABLED,
included_applications=["some-specific-app-id"],
included_users=["GuestsOrExternalUsers"],
built_in_controls=[ConditionalAccessGrantControl.MFA],
)
entra_client.conditional_access_policies = {policy.id: policy}
result = (
entra_conditional_access_policy_mfa_enforced_for_guest_users().execute()
)
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy requires MFA for guest users."
)
def test_policy_not_targeting_guests_fails(self):
"""Test FAIL when the policy does not target guest users."""
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(f"{CHECK_MODULE_PATH}.entra_client", new=entra_client),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_mfa_enforced_for_guest_users.entra_conditional_access_policy_mfa_enforced_for_guest_users import (
entra_conditional_access_policy_mfa_enforced_for_guest_users,
)
policy = build_policy(
display_name="MFA for Specific Users",
state=ConditionalAccessPolicyState.ENABLED,
included_users=[str(uuid4())],
built_in_controls=[ConditionalAccessGrantControl.MFA],
)
entra_client.conditional_access_policies = {policy.id: policy}
result = (
entra_conditional_access_policy_mfa_enforced_for_guest_users().execute()
)
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy requires MFA for guest users."
)
def test_policy_with_partial_guest_types_fails(self):
"""Test FAIL when policy only targets some guest types but not all six."""
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(f"{CHECK_MODULE_PATH}.entra_client", new=entra_client),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_mfa_enforced_for_guest_users.entra_conditional_access_policy_mfa_enforced_for_guest_users import (
entra_conditional_access_policy_mfa_enforced_for_guest_users,
)
policy = build_policy(
display_name="MFA for Some Guests",
state=ConditionalAccessPolicyState.ENABLED,
built_in_controls=[ConditionalAccessGrantControl.MFA],
included_guests_or_external_users=GuestsOrExternalUsers(
guest_or_external_user_types=[
GuestOrExternalUserType.B2B_COLLABORATION_GUEST,
GuestOrExternalUserType.INTERNAL_GUEST,
],
),
)
entra_client.conditional_access_policies = {policy.id: policy}
result = (
entra_conditional_access_policy_mfa_enforced_for_guest_users().execute()
)
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy requires MFA for guest users."
)
def test_policy_with_excluded_guest_types_fails(self):
"""Test FAIL when the policy excludes guest/external user types."""
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(f"{CHECK_MODULE_PATH}.entra_client", new=entra_client),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_mfa_enforced_for_guest_users.entra_conditional_access_policy_mfa_enforced_for_guest_users import (
entra_conditional_access_policy_mfa_enforced_for_guest_users,
)
policy = build_policy(
display_name="MFA for Guests with Exclusions",
state=ConditionalAccessPolicyState.ENABLED,
included_users=["All"],
built_in_controls=[ConditionalAccessGrantControl.MFA],
excluded_guests_or_external_users=GuestsOrExternalUsers(
guest_or_external_user_types=[
GuestOrExternalUserType.SERVICE_PROVIDER,
],
),
)
entra_client.conditional_access_policies = {policy.id: policy}
result = (
entra_conditional_access_policy_mfa_enforced_for_guest_users().execute()
)
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy requires MFA for guest users."
)
def test_policy_excluding_guests_via_excluded_users_fails(self):
"""Test FAIL when the policy excludes GuestsOrExternalUsers."""
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(f"{CHECK_MODULE_PATH}.entra_client", new=entra_client),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_mfa_enforced_for_guest_users.entra_conditional_access_policy_mfa_enforced_for_guest_users import (
entra_conditional_access_policy_mfa_enforced_for_guest_users,
)
policy = build_policy(
display_name="MFA for Users Except Guests",
state=ConditionalAccessPolicyState.ENABLED,
included_users=["All"],
excluded_users=["GuestsOrExternalUsers"],
built_in_controls=[ConditionalAccessGrantControl.MFA],
)
entra_client.conditional_access_policies = {policy.id: policy}
result = (
entra_conditional_access_policy_mfa_enforced_for_guest_users().execute()
)
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy requires MFA for guest users."
)
def test_policy_with_selected_external_tenants_fails(self):
"""Test FAIL when the policy only targets selected external tenants."""
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(f"{CHECK_MODULE_PATH}.entra_client", new=entra_client),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_mfa_enforced_for_guest_users.entra_conditional_access_policy_mfa_enforced_for_guest_users import (
entra_conditional_access_policy_mfa_enforced_for_guest_users,
)
policy = build_policy(
display_name="MFA for Selected External Tenants",
state=ConditionalAccessPolicyState.ENABLED,
built_in_controls=[ConditionalAccessGrantControl.MFA],
included_guests_or_external_users=GuestsOrExternalUsers(
guest_or_external_user_types=list(ALL_GUEST_USER_TYPES),
external_tenants_membership_kind=ExternalTenantsMembershipKind.ENUMERATED,
),
)
entra_client.conditional_access_policies = {policy.id: policy}
result = (
entra_conditional_access_policy_mfa_enforced_for_guest_users().execute()
)
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy requires MFA for guest users."
)
def test_reporting_only_policy_fails_with_detail(self):
"""Test FAIL with detail when the matching policy is in report-only mode."""
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(f"{CHECK_MODULE_PATH}.entra_client", new=entra_client),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_mfa_enforced_for_guest_users.entra_conditional_access_policy_mfa_enforced_for_guest_users import (
entra_conditional_access_policy_mfa_enforced_for_guest_users,
)
policy = build_policy(
display_name="MFA for Guests Report Only",
state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING,
included_users=["GuestsOrExternalUsers"],
built_in_controls=[ConditionalAccessGrantControl.MFA],
)
entra_client.conditional_access_policies = {policy.id: policy}
result = (
entra_conditional_access_policy_mfa_enforced_for_guest_users().execute()
)
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "Conditional Access Policy MFA for Guests Report Only targets guest users with MFA but is only in report-only mode."
)
assert result[0].resource_id == policy.id
assert result[0].resource_name == "MFA for Guests Report Only"
def test_no_application_conditions_fails(self):
"""Test FAIL when the policy has no application conditions."""
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(f"{CHECK_MODULE_PATH}.entra_client", new=entra_client),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_mfa_enforced_for_guest_users.entra_conditional_access_policy_mfa_enforced_for_guest_users import (
entra_conditional_access_policy_mfa_enforced_for_guest_users,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
policy_id = str(uuid4())
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name="No App Conditions",
conditions=Conditions(
application_conditions=None,
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["GuestsOrExternalUsers"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
operator=GrantControlOperator.OR,
authentication_strength=None,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(
is_enabled=False, mode="always"
),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=None,
),
),
state=ConditionalAccessPolicyState.ENABLED,
)
}
result = (
entra_conditional_access_policy_mfa_enforced_for_guest_users().execute()
)
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy requires MFA for guest users."
)
def test_no_mfa_grant_control_fails(self):
"""Test FAIL when the policy does not require MFA as a grant control."""
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(f"{CHECK_MODULE_PATH}.entra_client", new=entra_client),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_mfa_enforced_for_guest_users.entra_conditional_access_policy_mfa_enforced_for_guest_users import (
entra_conditional_access_policy_mfa_enforced_for_guest_users,
)
policy = build_policy(
display_name="Compliant Device for Guests",
state=ConditionalAccessPolicyState.ENABLED,
included_users=["GuestsOrExternalUsers"],
built_in_controls=[ConditionalAccessGrantControl.COMPLIANT_DEVICE],
)
entra_client.conditional_access_policies = {policy.id: policy}
result = (
entra_conditional_access_policy_mfa_enforced_for_guest_users().execute()
)
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy requires MFA for guest users."
)
@@ -0,0 +1,61 @@
from unittest import mock
from tests.providers.m365.m365_fixtures import set_mocked_m365_provider
class Test_exchange_organization_delicensing_resiliency_enabled_fixer:
def test_creates_new_powershell_session(self):
created_session = mock.MagicMock()
created_session.connect_exchange_online.return_value = True
created_session.execute.return_value = ""
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.services.exchange.exchange_organization_delicensing_resiliency_enabled.exchange_organization_delicensing_resiliency_enabled_fixer.M365PowerShell",
return_value=created_session,
) as mocked_powershell,
):
from prowler.providers.m365.services.exchange.exchange_organization_delicensing_resiliency_enabled.exchange_organization_delicensing_resiliency_enabled_fixer import (
fixer,
)
assert fixer()
mocked_powershell.assert_called_once()
created_session.connect_exchange_online.assert_called_once()
created_session.execute.assert_any_call(
"Set-OrganizationConfig -DelayedDelicensingEnabled $true",
timeout=30,
)
created_session.close.assert_called_once()
def test_logs_power_shell_execution_error(self):
created_session = mock.MagicMock()
created_session.connect_exchange_online.return_value = True
created_session.execute.return_value = "Access is denied."
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.services.exchange.exchange_organization_delicensing_resiliency_enabled.exchange_organization_delicensing_resiliency_enabled_fixer.M365PowerShell",
return_value=created_session,
),
mock.patch(
"prowler.providers.m365.services.exchange.exchange_organization_delicensing_resiliency_enabled.exchange_organization_delicensing_resiliency_enabled_fixer.logger.error",
) as mocked_logger_error,
):
from prowler.providers.m365.services.exchange.exchange_organization_delicensing_resiliency_enabled.exchange_organization_delicensing_resiliency_enabled_fixer import (
fixer,
)
assert not fixer()
mocked_logger_error.assert_any_call(
'PowerShell execution failed while running "Set-OrganizationConfig -DelayedDelicensingEnabled $true": Access is denied.'
)
created_session.close.assert_called_once()
@@ -0,0 +1,278 @@
from unittest import mock
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
ORGANIZATION_KWARGS = dict(
name="test-org",
guid="org-guid",
audit_disabled=False,
oauth_enabled=True,
mailtips_enabled=True,
mailtips_external_recipient_enabled=False,
mailtips_group_metrics_enabled=True,
mailtips_large_audience_threshold=25,
)
class Test_exchange_organization_delicensing_resiliency_enabled:
def test_no_organization(self):
exchange_client = mock.MagicMock()
exchange_client.audited_tenant = "audited_tenant"
exchange_client.audited_domain = DOMAIN
exchange_client.organization_config = None
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
mock.patch(
"prowler.providers.m365.services.exchange.exchange_organization_delicensing_resiliency_enabled.exchange_organization_delicensing_resiliency_enabled.exchange_client",
new=exchange_client,
),
):
from prowler.providers.m365.services.exchange.exchange_organization_delicensing_resiliency_enabled.exchange_organization_delicensing_resiliency_enabled import (
exchange_organization_delicensing_resiliency_enabled,
)
check = exchange_organization_delicensing_resiliency_enabled()
result = check.execute()
assert len(result) == 0
def test_delicensing_resiliency_disabled_above_threshold(self):
"""Disabled + >= 5000 total licenses -> FAIL (fixer confirms eligibility)."""
exchange_client = mock.MagicMock()
exchange_client.audited_tenant = "audited_tenant"
exchange_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
mock.patch(
"prowler.providers.m365.services.exchange.exchange_organization_delicensing_resiliency_enabled.exchange_organization_delicensing_resiliency_enabled.exchange_client",
new=exchange_client,
),
):
from prowler.providers.m365.services.exchange.exchange_organization_delicensing_resiliency_enabled.exchange_organization_delicensing_resiliency_enabled import (
exchange_organization_delicensing_resiliency_enabled,
)
from prowler.providers.m365.services.exchange.exchange_service import (
Organization,
)
exchange_client.organization_config = Organization(
**ORGANIZATION_KWARGS,
delayed_delicensing_enabled=False,
total_paid_licenses=6000,
)
check = exchange_organization_delicensing_resiliency_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "preventive FAIL" in result[0].status_extended
assert result[0].resource_name == "test-org"
assert result[0].resource_id == "org-guid"
assert result[0].location == "global"
def test_delicensing_resiliency_disabled_at_threshold(self):
"""Disabled + exactly 5000 total licenses -> FAIL."""
exchange_client = mock.MagicMock()
exchange_client.audited_tenant = "audited_tenant"
exchange_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
mock.patch(
"prowler.providers.m365.services.exchange.exchange_organization_delicensing_resiliency_enabled.exchange_organization_delicensing_resiliency_enabled.exchange_client",
new=exchange_client,
),
):
from prowler.providers.m365.services.exchange.exchange_organization_delicensing_resiliency_enabled.exchange_organization_delicensing_resiliency_enabled import (
exchange_organization_delicensing_resiliency_enabled,
)
from prowler.providers.m365.services.exchange.exchange_service import (
Organization,
)
exchange_client.organization_config = Organization(
**ORGANIZATION_KWARGS,
delayed_delicensing_enabled=False,
total_paid_licenses=5000,
)
check = exchange_organization_delicensing_resiliency_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
def test_delicensing_resiliency_disabled_below_threshold(self):
"""Disabled + < 5000 total licenses -> PASS (not applicable)."""
exchange_client = mock.MagicMock()
exchange_client.audited_tenant = "audited_tenant"
exchange_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
mock.patch(
"prowler.providers.m365.services.exchange.exchange_organization_delicensing_resiliency_enabled.exchange_organization_delicensing_resiliency_enabled.exchange_client",
new=exchange_client,
),
):
from prowler.providers.m365.services.exchange.exchange_organization_delicensing_resiliency_enabled.exchange_organization_delicensing_resiliency_enabled import (
exchange_organization_delicensing_resiliency_enabled,
)
from prowler.providers.m365.services.exchange.exchange_service import (
Organization,
)
exchange_client.organization_config = Organization(
**ORGANIZATION_KWARGS,
delayed_delicensing_enabled=False,
total_paid_licenses=4999,
)
check = exchange_organization_delicensing_resiliency_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert "not applicable" in result[0].status_extended
assert "4999 total licenses" in result[0].status_extended
def test_delicensing_resiliency_disabled_licenses_unknown(self):
"""Disabled + unknown license count -> FAIL."""
exchange_client = mock.MagicMock()
exchange_client.audited_tenant = "audited_tenant"
exchange_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
mock.patch(
"prowler.providers.m365.services.exchange.exchange_organization_delicensing_resiliency_enabled.exchange_organization_delicensing_resiliency_enabled.exchange_client",
new=exchange_client,
),
):
from prowler.providers.m365.services.exchange.exchange_organization_delicensing_resiliency_enabled.exchange_organization_delicensing_resiliency_enabled import (
exchange_organization_delicensing_resiliency_enabled,
)
from prowler.providers.m365.services.exchange.exchange_service import (
Organization,
)
exchange_client.organization_config = Organization(
**ORGANIZATION_KWARGS,
delayed_delicensing_enabled=False,
total_paid_licenses=None,
)
check = exchange_organization_delicensing_resiliency_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "preventive FAIL" in result[0].status_extended
def test_delicensing_resiliency_enabled(self):
"""Enabled -> PASS regardless of license count."""
exchange_client = mock.MagicMock()
exchange_client.audited_tenant = "audited_tenant"
exchange_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
mock.patch(
"prowler.providers.m365.services.exchange.exchange_organization_delicensing_resiliency_enabled.exchange_organization_delicensing_resiliency_enabled.exchange_client",
new=exchange_client,
),
):
from prowler.providers.m365.services.exchange.exchange_organization_delicensing_resiliency_enabled.exchange_organization_delicensing_resiliency_enabled import (
exchange_organization_delicensing_resiliency_enabled,
)
from prowler.providers.m365.services.exchange.exchange_service import (
Organization,
)
exchange_client.organization_config = Organization(
**ORGANIZATION_KWARGS,
delayed_delicensing_enabled=True,
total_paid_licenses=6000,
)
check = exchange_organization_delicensing_resiliency_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert "is enabled" in result[0].status_extended
assert result[0].resource == exchange_client.organization_config.dict()
assert result[0].resource_name == "test-org"
assert result[0].resource_id == "org-guid"
assert result[0].location == "global"
def test_delicensing_resiliency_enabled_below_threshold(self):
"""Enabled + below threshold -> still PASS (enabled always wins)."""
exchange_client = mock.MagicMock()
exchange_client.audited_tenant = "audited_tenant"
exchange_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
mock.patch(
"prowler.providers.m365.services.exchange.exchange_organization_delicensing_resiliency_enabled.exchange_organization_delicensing_resiliency_enabled.exchange_client",
new=exchange_client,
),
):
from prowler.providers.m365.services.exchange.exchange_organization_delicensing_resiliency_enabled.exchange_organization_delicensing_resiliency_enabled import (
exchange_organization_delicensing_resiliency_enabled,
)
from prowler.providers.m365.services.exchange.exchange_service import (
Organization,
)
exchange_client.organization_config = Organization(
**ORGANIZATION_KWARGS,
delayed_delicensing_enabled=True,
total_paid_licenses=100,
)
check = exchange_organization_delicensing_resiliency_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert "is enabled" in result[0].status_extended
@@ -161,6 +161,18 @@ def mock_exchange_get_shared_mailboxes(_):
]
async def mock_exchange_get_total_paid_licenses(_):
return 6000
async def mock_exchange_get_total_paid_licenses_none(_):
return None
@patch(
"prowler.providers.m365.services.exchange.exchange_service.Exchange._get_total_paid_licenses",
new=mock_exchange_get_total_paid_licenses,
)
class Test_Exchange_Service:
def test_get_client(self):
with (
@@ -201,6 +213,7 @@ class Test_Exchange_Service:
assert organization_config.mailtips_external_recipient_enabled is False
assert organization_config.mailtips_group_metrics_enabled is True
assert organization_config.mailtips_large_audience_threshold == 25
assert organization_config.total_paid_licenses == 6000
exchange_client.powershell.close()
@@ -481,3 +494,27 @@ class Test_Exchange_Service:
assert shared_mailboxes[1].identity == "info@contoso.com"
exchange_client.powershell.close()
def test_get_total_paid_licenses_none(self):
with (
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
mock.patch.object(
Exchange,
"_get_organization_config",
mock_exchange_get_organization_config,
),
mock.patch.object(
Exchange,
"_get_total_paid_licenses",
mock_exchange_get_total_paid_licenses_none,
),
):
exchange_client = Exchange(
set_mocked_m365_provider(
identity=M365IdentityInfo(tenant_domain=DOMAIN)
)
)
assert exchange_client.organization_config.total_paid_licenses is None
exchange_client.powershell.close()
@@ -0,0 +1,163 @@
from unittest import mock
from prowler.providers.m365.services.intune.intune_service import IntuneSettings
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
CHECK_MODULE_PATH = "prowler.providers.m365.services.intune.intune_device_compliance_policy_unassigned_devices_not_compliant_by_default.intune_device_compliance_policy_unassigned_devices_not_compliant_by_default"
class Test_intune_device_compliance_policy_unassigned_devices_not_compliant_by_default:
def test_secure_by_default_true(self):
intune_client = mock.MagicMock()
intune_client.audited_tenant = "audited_tenant"
intune_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(f"{CHECK_MODULE_PATH}.intune_client", new=intune_client),
):
from prowler.providers.m365.services.intune.intune_device_compliance_policy_unassigned_devices_not_compliant_by_default.intune_device_compliance_policy_unassigned_devices_not_compliant_by_default import (
intune_device_compliance_policy_unassigned_devices_not_compliant_by_default,
)
intune_client.settings = IntuneSettings(secure_by_default=True)
intune_client.verification_error = None
result = (
intune_device_compliance_policy_unassigned_devices_not_compliant_by_default().execute()
)
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].status_extended == (
"Intune built-in Device Compliance Policy marks devices "
"with no compliance policy assigned as Not compliant."
)
def test_secure_by_default_false(self):
intune_client = mock.MagicMock()
intune_client.audited_tenant = "audited_tenant"
intune_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(f"{CHECK_MODULE_PATH}.intune_client", new=intune_client),
):
from prowler.providers.m365.services.intune.intune_device_compliance_policy_unassigned_devices_not_compliant_by_default.intune_device_compliance_policy_unassigned_devices_not_compliant_by_default import (
intune_device_compliance_policy_unassigned_devices_not_compliant_by_default,
)
intune_client.settings = IntuneSettings(secure_by_default=False)
intune_client.verification_error = None
result = (
intune_device_compliance_policy_unassigned_devices_not_compliant_by_default().execute()
)
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].status_extended == (
"Intune built-in Device Compliance Policy marks devices "
"with no compliance policy assigned as Compliant. "
"Change the default to Not compliant in Intune settings."
)
def test_secure_by_default_none(self):
intune_client = mock.MagicMock()
intune_client.audited_tenant = "audited_tenant"
intune_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(f"{CHECK_MODULE_PATH}.intune_client", new=intune_client),
):
from prowler.providers.m365.services.intune.intune_device_compliance_policy_unassigned_devices_not_compliant_by_default.intune_device_compliance_policy_unassigned_devices_not_compliant_by_default import (
intune_device_compliance_policy_unassigned_devices_not_compliant_by_default,
)
intune_client.settings = IntuneSettings(secure_by_default=None)
intune_client.verification_error = None
result = (
intune_device_compliance_policy_unassigned_devices_not_compliant_by_default().execute()
)
assert len(result) == 1
assert result[0].status == "MANUAL"
assert result[0].status_extended == (
"Intune built-in Device Compliance Policy could not be verified "
"because Microsoft Graph did not return the secure-by-default "
"compliance setting."
)
def test_settings_is_none(self):
intune_client = mock.MagicMock()
intune_client.audited_tenant = "audited_tenant"
intune_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(f"{CHECK_MODULE_PATH}.intune_client", new=intune_client),
):
from prowler.providers.m365.services.intune.intune_device_compliance_policy_unassigned_devices_not_compliant_by_default.intune_device_compliance_policy_unassigned_devices_not_compliant_by_default import (
intune_device_compliance_policy_unassigned_devices_not_compliant_by_default,
)
intune_client.settings = None
intune_client.verification_error = None
result = (
intune_device_compliance_policy_unassigned_devices_not_compliant_by_default().execute()
)
assert len(result) == 1
assert result[0].status == "MANUAL"
assert result[0].status_extended == (
"Intune built-in Device Compliance Policy could not be verified "
"because Microsoft Graph did not return the secure-by-default "
"compliance setting."
)
def test_verification_error_returns_manual(self):
intune_client = mock.MagicMock()
intune_client.audited_tenant = "audited_tenant"
intune_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(f"{CHECK_MODULE_PATH}.intune_client", new=intune_client),
):
from prowler.providers.m365.services.intune.intune_device_compliance_policy_unassigned_devices_not_compliant_by_default.intune_device_compliance_policy_unassigned_devices_not_compliant_by_default import (
intune_device_compliance_policy_unassigned_devices_not_compliant_by_default,
)
intune_client.settings = None
intune_client.verification_error = (
"Could not read Microsoft Intune device management settings."
)
result = (
intune_device_compliance_policy_unassigned_devices_not_compliant_by_default().execute()
)
assert len(result) == 1
assert result[0].status == "MANUAL"
assert result[0].status_extended == (
"Intune built-in Device Compliance Policy could not be verified. "
"Could not read Microsoft Intune device management settings."
)
@@ -349,6 +349,60 @@ def test_intune_get_settings_null_settings():
assert settings.secure_by_default is None
def test_intune_get_settings_retries_without_select_when_settings_missing():
"""Test _get_settings retries without $select when settings are omitted."""
intune = Intune.__new__(Intune)
selected_response = SimpleNamespace(settings=None)
full_response = SimpleNamespace(settings=SimpleNamespace(secure_by_default=True))
mock_client = mock.MagicMock()
mock_client.device_management.get = AsyncMock(
side_effect=[selected_response, full_response]
)
intune.client = mock_client
loop = asyncio.new_event_loop()
try:
settings, error = loop.run_until_complete(intune._get_settings())
finally:
loop.close()
assert error is None
assert settings is not None
assert settings.secure_by_default is True
assert mock_client.device_management.get.await_count == 2
def test_intune_get_settings_retries_without_select_when_value_missing():
"""Test _get_settings retries without $select when secure_by_default is omitted."""
intune = Intune.__new__(Intune)
selected_response = SimpleNamespace(
settings=SimpleNamespace(secure_by_default=None)
)
full_response = SimpleNamespace(settings=SimpleNamespace(secure_by_default=False))
mock_client = mock.MagicMock()
mock_client.device_management.get = AsyncMock(
side_effect=[selected_response, full_response]
)
intune.client = mock_client
loop = asyncio.new_event_loop()
try:
settings, error = loop.run_until_complete(intune._get_settings())
finally:
loop.close()
assert error is None
assert settings is not None
assert settings.secure_by_default is False
assert mock_client.device_management.get.await_count == 2
def test_intune_get_settings_exception():
"""Test _get_settings handles exceptions gracefully."""
intune = Intune.__new__(Intune)