mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-29 23:04:23 +00:00
chore: merge master and resolve CHANGELOG.md conflict
This commit is contained in:
414 files changed
+39499
-15901
No files matched your search
@@ -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]
|
||||
@@ -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"]}
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
+357
@@ -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
|
||||
+558
@@ -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"
|
||||
+461
@@ -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
|
||||
+509
@@ -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
|
||||
Whitespace-only changes.
+240
@@ -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
|
||||
Whitespace-only changes.
+449
@@ -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 = {
|
||||
|
||||
Whitespace-only changes.
+180
@@ -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 (
|
||||
|
||||
+156
@@ -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
|
||||
+122
@@ -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
|
||||
+122
@@ -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
|
||||
+158
@@ -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
|
||||
+122
@@ -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
|
||||
+124
@@ -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
|
||||
+158
@@ -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
|
||||
+128
@@ -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
|
||||
+122
@@ -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
|
||||
+154
@@ -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
|
||||
+128
@@ -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
|
||||
+858
@@ -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"
|
||||
+6
-6
@@ -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
|
||||
|
||||
+702
@@ -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
|
||||
+697
@@ -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
|
||||
+711
@@ -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
|
||||
+705
@@ -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."
|
||||
)
|
||||
+61
@@ -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()
|
||||
+278
@@ -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()
|
||||
+163
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user