merge branch 'master' into PRWLR-5989-ensure-that-spf-records-are-published-for-all-exchange

This commit is contained in:
Daniel Barranquero committed 2025-05-21 10:28:25 +02:00
commit 8f72dd26ba
27 files changed
+727 -36

No files matched your search

@@ -0,0 +1,67 @@
name: Create Backport Label
on:
release:
types: [published]
jobs:
create_label:
runs-on: ubuntu-latest
permissions:
contents: write
issues: write
steps:
- name: Create backport label
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
OWNER_REPO: ${{ github.repository }}
run: |
VERSION_ONLY=${RELEASE_TAG#v} # Remove 'v' prefix if present (e.g., v3.2.0 -> 3.2.0)
# Check if it's a minor version (X.Y.0)
if [[ "$VERSION_ONLY" =~ ^[0-9]+\.[0-9]+\.0$ ]]; then
echo "Release ${RELEASE_TAG} (version ${VERSION_ONLY}) is a minor version. Proceeding to create backport label."
TWO_DIGIT_VERSION=${VERSION_ONLY%.0} # Extract X.Y from X.Y.0 (e.g., 5.6 from 5.6.0)
FINAL_LABEL_NAME="backport-to-v${TWO_DIGIT_VERSION}"
FINAL_DESCRIPTION="Backport PR to the v${TWO_DIGIT_VERSION} branch"
echo "Effective label name will be: ${FINAL_LABEL_NAME}"
echo "Effective description will be: ${FINAL_DESCRIPTION}"
# Check if the label already exists
STATUS_CODE=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: token ${GITHUB_TOKEN}" "https://api.github.com/repos/${OWNER_REPO}/labels/${FINAL_LABEL_NAME}")
if [ "${STATUS_CODE}" -eq 200 ]; then
echo "Label '${FINAL_LABEL_NAME}' already exists."
elif [ "${STATUS_CODE}" -eq 404 ]; then
echo "Label '${FINAL_LABEL_NAME}' does not exist. Creating it..."
# Prepare JSON data payload
JSON_DATA=$(printf '{"name":"%s","description":"%s","color":"B60205"}' "${FINAL_LABEL_NAME}" "${FINAL_DESCRIPTION}")
CREATE_STATUS_CODE=$(curl -s -o /tmp/curl_create_response.json -w "%{http_code}" -X POST \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token ${GITHUB_TOKEN}" \
--data "${JSON_DATA}" \
"https://api.github.com/repos/${OWNER_REPO}/labels")
CREATE_RESPONSE_BODY=$(cat /tmp/curl_create_response.json)
rm -f /tmp/curl_create_response.json
if [ "$CREATE_STATUS_CODE" -eq 201 ]; then
echo "Label '${FINAL_LABEL_NAME}' created successfully."
else
echo "Error creating label '${FINAL_LABEL_NAME}'. Status: $CREATE_STATUS_CODE"
echo "Response: $CREATE_RESPONSE_BODY"
exit 1
fi
else
echo "Error checking for label '${FINAL_LABEL_NAME}'. HTTP Status: ${STATUS_CODE}"
exit 1
fi
else
echo "Release ${RELEASE_TAG} (version ${VERSION_ONLY}) is not a minor version. Skipping backport label creation."
exit 0
fi
+3
View File
@@ -9,6 +9,8 @@ All notable changes to the **Prowler SDK** are documented in this file.
- Allow setting cluster name in in-cluster mode in Kubernetes. [(#7695)](https://github.com/prowler-cloud/prowler/pull/7695)
- Add Prowler ThreatScore for M365 provider. [(#7692)](https://github.com/prowler-cloud/prowler/pull/7692)
- Add new check `defender_domain_spf_records_published`. [(#7724)](https://github.com/prowler-cloud/prowler/pull/7724)
- Add new check `admincenter_organization_customer_lockbox_enabled`. [(#7732)](https://github.com/prowler-cloud/prowler/pull/7732)
- Add new check `admincenter_external_calendar_sharing_disabled`. [(#7733)](https://github.com/prowler-cloud/prowler/pull/7733)
- Add GitHub provider. [(#5787)](https://github.com/prowler-cloud/prowler/pull/5787)
- Add `repository_default_branch_requires_multiple_approvals` check for GitHub provider. [(#6160)](https://github.com/prowler-cloud/prowler/pull/6160)
- Add `repository_default_branch_protection_enabled` check for GitHub provider. [(#6161)](https://github.com/prowler-cloud/prowler/pull/6161)
@@ -28,6 +30,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- Enhance defender policies checks logic. [(#7719)](https://github.com/prowler-cloud/prowler/pull/7719)
- Update and upgrade CIS for all the providers [(#7738)](https://github.com/prowler-cloud/prowler/pull/7738)
- Cover policies with conditions with SNS endpoint in `sns_topics_not_publicly_accessible`. [(#7750)](https://github.com/prowler-cloud/prowler/pull/7750)
- Fix `m365_powershell test_credentials` to use sanitized credentials. [(#7761)](https://github.com/prowler-cloud/prowler/pull/7761)
- Change severity logic for `ec2_securitygroup_allow_ingress_from_internet_to_all_ports` check. [(#7764)](https://github.com/prowler-cloud/prowler/pull/7764)
---
+6 -2
View File
@@ -185,7 +185,9 @@
{
"Id": "1.3.3",
"Description": "External calendar sharing allows an administrator to enable the ability for users to share calendars with anyone outside of the organization. Outside users will be sent a URL that can be used to view the calendar.",
"Checks": [],
"Checks": [
"admincenter_external_calendar_sharing_disabled"
],
"Attributes": [
{
"Section": "1 Microsoft 365 admin center",
@@ -248,7 +250,9 @@
{
"Id": "1.3.6",
"Description": "Customer Lockbox is a security feature that provides an additional layer of control and transparency to customer data in Microsoft 365. It offers an approval process for Microsoft support personnel to access organization data and creates an audited trail to meet compliance requirements.",
"Checks": [],
"Checks": [
"admincenter_organization_customer_lockbox_enabled"
],
"Attributes": [
{
"Section": "1 Microsoft 365 admin center",
+1 -1
View File
@@ -12,7 +12,7 @@ from prowler.lib.logger import logger
timestamp = datetime.today()
timestamp_utc = datetime.now(timezone.utc).replace(tzinfo=timezone.utc)
prowler_version = "5.7.0"
prowler_version = "5.8.0"
html_logo_url = "https://github.com/prowler-cloud/prowler/"
square_logo_img = "https://prowler.com/wp-content/uploads/logo-html.png"
aws_logo = "https://user-images.githubusercontent.com/38561120/235953920-3e3fba08-0795-41dc-b480-9bea57db9f2e.png"
@@ -87,10 +87,10 @@ class M365PowerShell(PowerShellSession):
bool: True if credentials are valid and authentication succeeds, False otherwise.
"""
self.execute(
f'$securePassword = "{credentials.passwd}" | ConvertTo-SecureString'
f'$securePassword = "{self.sanitize(credentials.passwd)}" | ConvertTo-SecureString'
)
self.execute(
f'$credential = New-Object System.Management.Automation.PSCredential("{credentials.user}", $securePassword)\n'
f'$credential = New-Object System.Management.Automation.PSCredential("{self.sanitize(credentials.user)}", $securePassword)'
)
decrypted_password = self.execute(
'Write-Output "$($credential.GetNetworkCredential().Password)"'
@@ -685,6 +685,24 @@ class M365PowerShell(PowerShellSession):
"""
return self.execute("Get-TransportConfig | ConvertTo-Json", json_parse=True)
def get_sharing_policy(self) -> dict:
"""
Get Exchange Online Sharing Policy.
Retrieves the current sharing policy settings for Exchange Online.
Returns:
dict: Sharing policy settings in JSON format.
Example:
>>> get_sharing_policy()
{
"Identity": "Default",
"Enabled": true
}
"""
return self.execute("Get-SharingPolicy | ConvertTo-Json", json_parse=True)
# This function is used to install the required M365 PowerShell modules in Docker containers
def initialize_m365_powershell_modules():
@@ -0,0 +1,32 @@
{
"Provider": "m365",
"CheckID": "admincenter_external_calendar_sharing_disabled",
"CheckTitle": "Ensure external sharing of calendars is disabled",
"CheckType": [],
"ServiceName": "admincenter",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "Sharing Policy",
"Description": "Restrict the ability for users to share their calendars externally in Microsoft 365. This prevents users from sending calendar sharing links to external recipients, reducing information exposure.",
"Risk": "Allowing calendar sharing outside the organization can help attackers build knowledge of personnel availability, relationships, and activity patterns, aiding social engineering or targeted attacks.",
"RelatedUrl": "https://learn.microsoft.com/en-us/microsoft-365/admin/manage/share-calendars-with-external-users?view=o365-worldwide",
"Remediation": {
"Code": {
"CLI": "Set-SharingPolicy -Identity \"Default Sharing Policy\" -Enabled $False",
"NativeIaC": "",
"Other": "1. Navigate to https://admin.microsoft.com. 2. Click Settings > Org settings. 3. Select Calendar in the Services section. 4. Uncheck 'Let your users share their calendars with people outside of your organization who have Office 365 or Exchange'. 5. Click Save.",
"Terraform": ""
},
"Recommendation": {
"Text": "Disable external calendar sharing by setting the Default Sharing Policy to disabled.",
"Url": "https://learn.microsoft.com/en-us/microsoft-365/admin/manage/share-calendars-with-external-users?view=o365-worldwide"
}
},
"Categories": [
"e5"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,52 @@
from typing import List
from prowler.lib.check.models import Check, CheckReportM365
from prowler.providers.m365.services.admincenter.admincenter_client import (
admincenter_client,
)
class admincenter_external_calendar_sharing_disabled(Check):
"""
Ensure that external calendar sharing is disabled for the organization.
Disabling external calendar sharing restricts the ability for users to share their
calendars externally in Microsoft 365. This prevents users from sending calendar
sharing links to external recipients, reducing information exposure.
Attributes:
metadata: Metadata associated with the check (inherited from Check).
"""
def execute(self) -> List[CheckReportM365]:
"""
Execute the check for external calendar sharing in Microsoft 365.
This method checks if external calendar sharing is disabled in the organization configuration.
Returns:
List[CheckReportM365]: A list of reports containing the result of the check.
"""
findings = []
sharing_policy = admincenter_client.sharing_policy
if sharing_policy:
report = CheckReportM365(
metadata=self.metadata(),
resource=sharing_policy,
resource_name=sharing_policy.name,
resource_id=sharing_policy.guid,
)
report.status = "FAIL"
report.status_extended = (
"External calendar sharing is enabled at the organization level."
)
if not sharing_policy.enabled:
report.status = "PASS"
report.status_extended = (
"External calendar sharing is disabled at the organization level."
)
findings.append(report)
return findings
@@ -0,0 +1,32 @@
{
"Provider": "m365",
"CheckID": "admincenter_organization_customer_lockbox_enabled",
"CheckTitle": "Ensure that customer lockbox is enabled for the organization",
"CheckType": [],
"ServiceName": "admincenter",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "Exchange Organization Configuration",
"Description": "Customer Lockbox ensures that Microsoft support engineers cannot access content in your tenant to perform a service operation without explicit approval. This feature provides an additional layer of control and transparency over data access requests.",
"Risk": "If Customer Lockbox is not enabled, Microsoft support personnel can access your organization's data for troubleshooting without explicit approval, potentially increasing the risk of unauthorized access or data exfiltration.",
"RelatedUrl": "https://learn.microsoft.com/en-us/azure/security/fundamentals/customer-lockbox-overview",
"Remediation": {
"Code": {
"CLI": "Set-OrganizationConfig -CustomerLockBoxEnabled $true",
"NativeIaC": "",
"Other": "1. Navigate to Microsoft 365 admin center https://admin.microsoft.com. 2. Click Settings > Org settings. 3. Select the Security & privacy tab. 4. Click Customer lockbox. 5. Check the box 'Require approval for all data access requests'. 6. Click Save.",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable the Customer Lockbox feature to ensure explicit approval is required before Microsoft engineers can access your data during support operations.",
"Url": "https://learn.microsoft.com/en-us/azure/security/fundamentals/customer-lockbox-overview"
}
},
"Categories": [
"e5"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,52 @@
from typing import List
from prowler.lib.check.models import Check, CheckReportM365
from prowler.providers.m365.services.admincenter.admincenter_client import (
admincenter_client,
)
class admincenter_organization_customer_lockbox_enabled(Check):
"""
Ensure the customer lockbox feature is enabled.
Customer Lockbox ensures that Microsoft support engineers cannot access content
in your tenant to perform a service operation without explicit approval. This feature
provides an additional layer of control and transparency over data access requests.
Attributes:
metadata: Metadata associated with the check (inherited from Check).
"""
def execute(self) -> List[CheckReportM365]:
"""
Execute the check for the Customer Lockbox feature in Microsoft 365.
This method checks if the Customer Lockbox feature is enabled in the organization configuration.
Returns:
List[CheckReportM365]: A list of reports containing the result of the check.
"""
findings = []
organization_config = admincenter_client.organization_config
if organization_config:
report = CheckReportM365(
metadata=self.metadata(),
resource=organization_config,
resource_name=organization_config.name,
resource_id=organization_config.guid,
)
report.status = "FAIL"
report.status_extended = (
"Customer Lockbox is not enabled at organization level."
)
if organization_config.customer_lockbox_enabled:
report.status = "PASS"
report.status_extended = (
"Customer Lockbox is enabled at organization level."
)
findings.append(report)
return findings
@@ -11,6 +11,13 @@ from prowler.providers.m365.m365_provider import M365Provider
class AdminCenter(M365Service):
def __init__(self, provider: M365Provider):
super().__init__(provider)
self.organization_config = None
self.sharing_policy = None
if self.powershell:
self.powershell.connect_exchange_online()
self.organization_config = self._get_organization_config()
self.sharing_policy = self._get_sharing_policy()
self.powershell.close()
loop = get_event_loop()
@@ -29,6 +36,42 @@ class AdminCenter(M365Service):
self.groups = attributes[1]
self.domains = attributes[2]
def _get_organization_config(self):
logger.info("Microsoft365 - Getting Exchange Organization configuration...")
organization_config = None
try:
organization_configuration = self.powershell.get_organization_config()
if organization_configuration:
organization_config = Organization(
name=organization_configuration.get("Name", ""),
guid=organization_configuration.get("Guid", ""),
customer_lockbox_enabled=organization_configuration.get(
"CustomerLockboxEnabled", False
),
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return organization_config
def _get_sharing_policy(self):
logger.info("M365 - Getting sharing policy...")
sharing_policy = None
try:
sharing_policy_data = self.powershell.get_sharing_policy()
if sharing_policy_data:
sharing_policy = SharingPolicy(
name=sharing_policy_data.get("Name", ""),
guid=sharing_policy_data.get("Guid", ""),
enabled=sharing_policy_data.get("Enabled", False),
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return sharing_policy
async def _get_users(self):
logger.info("M365 - Getting users...")
users = {}
@@ -163,3 +206,15 @@ class Group(BaseModel):
class Domain(BaseModel):
id: str
password_validity_period: int
class Organization(BaseModel):
name: str
guid: str
customer_lockbox_enabled: bool
class SharingPolicy(BaseModel):
name: str
guid: str
enabled: bool
+1 -1
View File
@@ -65,7 +65,7 @@ maintainers = [{name = "Prowler Engineering", email = "engineering@prowler.com"}
name = "prowler"
readme = "README.md"
requires-python = ">3.9.1,<3.13"
version = "5.7.0"
version = "5.8.0"
[project.scripts]
prowler = "prowler.__main__:prowler"
@@ -145,7 +145,7 @@ class Testm365PowerShell:
f'$securePassword = "{credentials.passwd}" | ConvertTo-SecureString'
)
session.execute.assert_any_call(
f'$credential = New-Object System.Management.Automation.PSCredential("{credentials.user}", $securePassword)\n'
f'$credential = New-Object System.Management.Automation.PSCredential("{session.sanitize(credentials.user)}", $securePassword)'
)
session.execute.assert_any_call(
'Write-Output "$($credential.GetNetworkCredential().Password)"'
@@ -0,0 +1,120 @@
from unittest import mock
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
class Test_admincenter_external_calendar_sharing_disabled:
def test_admincenter_no_sharing_policy(self):
admincenter_client = mock.MagicMock()
admincenter_client.audited_tenant = "audited_tenant"
admincenter_client.audited_domain = DOMAIN
admincenter_client.sharing_policy = 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.admincenter.admincenter_external_calendar_sharing_disabled.admincenter_external_calendar_sharing_disabled.admincenter_client",
new=admincenter_client,
),
):
from prowler.providers.m365.services.admincenter.admincenter_external_calendar_sharing_disabled.admincenter_external_calendar_sharing_disabled import (
admincenter_external_calendar_sharing_disabled,
)
check = admincenter_external_calendar_sharing_disabled()
result = check.execute()
assert len(result) == 0
def test_admincenter_calendar_sharing_disabled(self):
admincenter_client = mock.MagicMock()
admincenter_client.audited_tenant = "audited_tenant"
admincenter_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.admincenter.admincenter_external_calendar_sharing_disabled.admincenter_external_calendar_sharing_disabled.admincenter_client",
new=admincenter_client,
),
):
from prowler.providers.m365.services.admincenter.admincenter_external_calendar_sharing_disabled.admincenter_external_calendar_sharing_disabled import (
admincenter_external_calendar_sharing_disabled,
)
from prowler.providers.m365.services.admincenter.admincenter_service import (
SharingPolicy,
)
admincenter_client.sharing_policy = SharingPolicy(
name="test-org",
guid="org-guid",
enabled=False,
)
check = admincenter_external_calendar_sharing_disabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "External calendar sharing is disabled at the organization level."
)
assert result[0].resource == admincenter_client.sharing_policy.dict()
assert result[0].resource_name == "test-org"
assert result[0].resource_id == "org-guid"
assert result[0].location == "global"
def test_admincenter_calendar_sharing_enabled(self):
admincenter_client = mock.MagicMock()
admincenter_client.audited_tenant = "audited_tenant"
admincenter_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.admincenter.admincenter_external_calendar_sharing_disabled.admincenter_external_calendar_sharing_disabled.admincenter_client",
new=admincenter_client,
),
):
from prowler.providers.m365.services.admincenter.admincenter_external_calendar_sharing_disabled.admincenter_external_calendar_sharing_disabled import (
admincenter_external_calendar_sharing_disabled,
)
from prowler.providers.m365.services.admincenter.admincenter_service import (
SharingPolicy,
)
admincenter_client.sharing_policy = SharingPolicy(
name="test-org",
guid="org-guid",
enabled=True,
)
check = admincenter_external_calendar_sharing_disabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "External calendar sharing is enabled at the organization level."
)
assert result[0].resource == admincenter_client.sharing_policy.dict()
assert result[0].resource_name == "test-org"
assert result[0].resource_id == "org-guid"
assert result[0].location == "global"
@@ -15,6 +15,9 @@ class Test_admincenter_groups_not_public_visibility:
"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.admincenter.admincenter_groups_not_public_visibility.admincenter_groups_not_public_visibility.admincenter_client",
new=admincenter_client,
@@ -40,6 +43,9 @@ class Test_admincenter_groups_not_public_visibility:
"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.admincenter.admincenter_groups_not_public_visibility.admincenter_groups_not_public_visibility.admincenter_client",
new=admincenter_client,
@@ -78,6 +84,9 @@ class Test_admincenter_groups_not_public_visibility:
"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.admincenter.admincenter_groups_not_public_visibility.admincenter_groups_not_public_visibility.admincenter_client",
new=admincenter_client,
@@ -0,0 +1,120 @@
from unittest import mock
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
class Test_admincenter_organization_customer_lockbox_enabled:
def test_admincenter_no_org_config(self):
admincenter_client = mock.MagicMock()
admincenter_client.audited_tenant = "audited_tenant"
admincenter_client.audited_domain = DOMAIN
admincenter_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.admincenter.admincenter_organization_customer_lockbox_enabled.admincenter_organization_customer_lockbox_enabled.admincenter_client",
new=admincenter_client,
),
):
from prowler.providers.m365.services.admincenter.admincenter_organization_customer_lockbox_enabled.admincenter_organization_customer_lockbox_enabled import (
admincenter_organization_customer_lockbox_enabled,
)
check = admincenter_organization_customer_lockbox_enabled()
result = check.execute()
assert len(result) == 0
def test_admincenter_customer_lockbox_enabled(self):
admincenter_client = mock.MagicMock()
admincenter_client.audited_tenant = "audited_tenant"
admincenter_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.admincenter.admincenter_organization_customer_lockbox_enabled.admincenter_organization_customer_lockbox_enabled.admincenter_client",
new=admincenter_client,
),
):
from prowler.providers.m365.services.admincenter.admincenter_organization_customer_lockbox_enabled.admincenter_organization_customer_lockbox_enabled import (
admincenter_organization_customer_lockbox_enabled,
)
from prowler.providers.m365.services.admincenter.admincenter_service import (
Organization,
)
admincenter_client.organization_config = Organization(
name="test-org",
guid="org-guid",
customer_lockbox_enabled=True,
)
check = admincenter_organization_customer_lockbox_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "Customer Lockbox is enabled at organization level."
)
assert result[0].resource == admincenter_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_admincenter_customer_lockbox_disabled(self):
admincenter_client = mock.MagicMock()
admincenter_client.audited_tenant = "audited_tenant"
admincenter_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.admincenter.admincenter_organization_customer_lockbox_enabled.admincenter_organization_customer_lockbox_enabled.admincenter_client",
new=admincenter_client,
),
):
from prowler.providers.m365.services.admincenter.admincenter_organization_customer_lockbox_enabled.admincenter_organization_customer_lockbox_enabled import (
admincenter_organization_customer_lockbox_enabled,
)
from prowler.providers.m365.services.admincenter.admincenter_service import (
Organization,
)
admincenter_client.organization_config = Organization(
name="test-org",
guid="org-guid",
customer_lockbox_enabled=False,
)
check = admincenter_organization_customer_lockbox_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "Customer Lockbox is not enabled at organization level."
)
assert result[0].resource == admincenter_client.organization_config.dict()
assert result[0].resource_name == "test-org"
assert result[0].resource_id == "org-guid"
assert result[0].location == "global"
@@ -1,3 +1,4 @@
from unittest import mock
from unittest.mock import patch
from prowler.providers.m365.models import M365IdentityInfo
@@ -5,6 +6,8 @@ from prowler.providers.m365.services.admincenter.admincenter_service import (
AdminCenter,
DirectoryRole,
Group,
Organization,
SharingPolicy,
User,
)
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
@@ -36,6 +39,22 @@ async def mock_admincenter_get_groups(_):
}
def mock_admincenter_get_organization(_):
return Organization(
guid="id-1",
name="Test",
customer_lockbox_enabled=False,
)
def mock_admincenter_get_sharing_policy(_):
return SharingPolicy(
guid="id-1",
name="Test",
enabled=False,
)
@patch(
"prowler.providers.m365.services.admincenter.admincenter_service.AdminCenter._get_users",
new=mock_admincenter_get_users,
@@ -48,32 +67,97 @@ async def mock_admincenter_get_groups(_):
"prowler.providers.m365.services.admincenter.admincenter_service.AdminCenter._get_groups",
new=mock_admincenter_get_groups,
)
@patch(
"prowler.providers.m365.services.admincenter.admincenter_service.AdminCenter._get_organization_config",
new=mock_admincenter_get_organization,
)
@patch(
"prowler.providers.m365.services.admincenter.admincenter_service.AdminCenter._get_sharing_policy",
new=mock_admincenter_get_sharing_policy,
)
class Test_AdminCenter_Service:
def test_get_client(self):
admincenter_client = AdminCenter(
set_mocked_m365_provider(identity=M365IdentityInfo(tenant_domain=DOMAIN))
)
assert admincenter_client.client.__class__.__name__ == "GraphServiceClient"
with (
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
):
admincenter_client = AdminCenter(
set_mocked_m365_provider(
identity=M365IdentityInfo(tenant_domain=DOMAIN)
)
)
assert admincenter_client.client.__class__.__name__ == "GraphServiceClient"
def test_get_users(self):
admincenter_client = AdminCenter(set_mocked_m365_provider())
assert len(admincenter_client.users) == 1
assert admincenter_client.users["user-1@tenant1.es"].id == "id-1"
assert admincenter_client.users["user-1@tenant1.es"].name == "User 1"
with (
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
):
admincenter_client = AdminCenter(set_mocked_m365_provider())
assert len(admincenter_client.users) == 1
assert admincenter_client.users["user-1@tenant1.es"].id == "id-1"
assert admincenter_client.users["user-1@tenant1.es"].name == "User 1"
def test_get_group_settings(self):
admincenter_client = AdminCenter(set_mocked_m365_provider())
assert len(admincenter_client.groups) == 1
assert admincenter_client.groups["id-1"].id == "id-1"
assert admincenter_client.groups["id-1"].name == "Test"
assert admincenter_client.groups["id-1"].visibility == "Public"
with (
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
):
admincenter_client = AdminCenter(set_mocked_m365_provider())
assert len(admincenter_client.groups) == 1
assert admincenter_client.groups["id-1"].id == "id-1"
assert admincenter_client.groups["id-1"].name == "Test"
assert admincenter_client.groups["id-1"].visibility == "Public"
def test_get_directory_roles(self):
admincenter_client = AdminCenter(set_mocked_m365_provider())
assert (
admincenter_client.directory_roles["GlobalAdministrator"].id
== "id-directory-role"
)
assert (
len(admincenter_client.directory_roles["GlobalAdministrator"].members) == 0
)
with (
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
):
admincenter_client = AdminCenter(set_mocked_m365_provider())
assert (
admincenter_client.directory_roles["GlobalAdministrator"].id
== "id-directory-role"
)
assert (
len(admincenter_client.directory_roles["GlobalAdministrator"].members)
== 0
)
def test_get_organization(self):
with (
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
):
admincenter_client = AdminCenter(
set_mocked_m365_provider(
identity=M365IdentityInfo(tenant_domain=DOMAIN)
)
)
assert admincenter_client.organization_config.guid == "id-1"
assert admincenter_client.organization_config.name == "Test"
assert (
admincenter_client.organization_config.customer_lockbox_enabled is False
)
admincenter_client.powershell.close()
def test_get_sharing_policy(self):
with (
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
):
admincenter_client = AdminCenter(
set_mocked_m365_provider(
identity=M365IdentityInfo(tenant_domain=DOMAIN)
)
)
assert admincenter_client.sharing_policy.guid == "id-1"
assert admincenter_client.sharing_policy.name == "Test"
assert admincenter_client.sharing_policy.enabled is False
admincenter_client.powershell.close()
@@ -15,6 +15,9 @@ class Test_admincenter_settings_password_never_expire:
"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.admincenter.admincenter_settings_password_never_expire.admincenter_settings_password_never_expire.admincenter_client",
new=admincenter_client,
@@ -40,6 +43,9 @@ class Test_admincenter_settings_password_never_expire:
"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.admincenter.admincenter_settings_password_never_expire.admincenter_settings_password_never_expire.admincenter_client",
new=admincenter_client,
@@ -81,6 +87,9 @@ class Test_admincenter_settings_password_never_expire:
"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.admincenter.admincenter_settings_password_never_expire.admincenter_settings_password_never_expire.admincenter_client",
new=admincenter_client,
@@ -15,6 +15,9 @@ class Test_admincenter_users_admins_reduced_license_footprint:
"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.admincenter.admincenter_users_admins_reduced_license_footprint.admincenter_users_admins_reduced_license_footprint.admincenter_client",
new=admincenter_client,
@@ -40,6 +43,9 @@ class Test_admincenter_users_admins_reduced_license_footprint:
"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.admincenter.admincenter_users_admins_reduced_license_footprint.admincenter_users_admins_reduced_license_footprint.admincenter_client",
new=admincenter_client,
@@ -77,6 +83,9 @@ class Test_admincenter_users_admins_reduced_license_footprint:
"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.admincenter.admincenter_users_admins_reduced_license_footprint.admincenter_users_admins_reduced_license_footprint.admincenter_client",
new=admincenter_client,
@@ -123,6 +132,9 @@ class Test_admincenter_users_admins_reduced_license_footprint:
"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.admincenter.admincenter_users_admins_reduced_license_footprint.admincenter_users_admins_reduced_license_footprint.admincenter_client",
new=admincenter_client,
@@ -169,6 +181,9 @@ class Test_admincenter_users_admins_reduced_license_footprint:
"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.admincenter.admincenter_users_admins_reduced_license_footprint.admincenter_users_admins_reduced_license_footprint.admincenter_client",
new=admincenter_client,
@@ -15,6 +15,9 @@ class Test_admincenter_users_between_two_and_four_global_admins:
"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.admincenter.admincenter_users_between_two_and_four_global_admins.admincenter_users_between_two_and_four_global_admins.admincenter_client",
new=admincenter_client,
@@ -40,6 +43,9 @@ class Test_admincenter_users_between_two_and_four_global_admins:
"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.admincenter.admincenter_users_between_two_and_four_global_admins.admincenter_users_between_two_and_four_global_admins.admincenter_client",
new=admincenter_client,
@@ -91,6 +97,9 @@ class Test_admincenter_users_between_two_and_four_global_admins:
"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.admincenter.admincenter_users_between_two_and_four_global_admins.admincenter_users_between_two_and_four_global_admins.admincenter_client",
new=admincenter_client,
@@ -153,6 +162,9 @@ class Test_admincenter_users_between_two_and_four_global_admins:
"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.admincenter.admincenter_users_between_two_and_four_global_admins.admincenter_users_between_two_and_four_global_admins.admincenter_client",
new=admincenter_client,
+2 -1
View File
@@ -7,7 +7,8 @@ All notable changes to the **Prowler UI** are documented in this file.
### 🐞 Fixes
- Added validation to AWS IAM role. [(#7787)](https://github.com/prowler-cloud/prowler/pull/7787)
- Retrieve more than 10 providers in /scans, /manage-groups and /findings pages. [(#7793)](https://github.com/prowler-cloud/prowler/pull/7793)
---
## [v1.7.0] (Prowler v5.7.0)
+1 -1
View File
@@ -43,7 +43,7 @@ export default async function Findings({
sort: encodedSort,
filters,
}),
getProviders({}),
getProviders({ pageSize: 50 }),
getScans({}),
]);
+2 -2
View File
@@ -58,7 +58,7 @@ export default function ManageGroupsPage({
}
const SSRAddGroupForm = async () => {
const providersResponse = await getProviders({});
const providersResponse = await getProviders({ pageSize: 50 });
const rolesResponse = await getRoles({});
const providersData =
@@ -95,7 +95,7 @@ const SSRDataEditGroup = async ({
return <div>Provider group not found</div>;
}
const providersResponse = await getProviders({});
const providersResponse = await getProviders({ pageSize: 50 });
const rolesResponse = await getRoles({});
const providersList =
+2
View File
@@ -29,6 +29,7 @@ export default async function Scans({
filters: {
"filter[connected]": true,
},
pageSize: 50,
});
const providerInfo =
@@ -42,6 +43,7 @@ export default async function Scans({
const providersCountConnected = await getProviders({
filters: { "filter[connected]": true },
pageSize: 50,
});
const thereIsNoProviders =
!providersCountConnected?.data || providersCountConnected.data.length === 0;
@@ -115,13 +115,13 @@ export const FindingDetail = ({
<SeverityBadge severity={attributes.severity || "-"} />
</InfoField>
</div>
<InfoField label="ID" variant="simple">
<InfoField label="Finding ID" variant="simple">
<CodeSnippet value={findingDetails.id} />
</InfoField>
<InfoField label="Check ID" variant="simple">
<CodeSnippet value={attributes.check_id} />
</InfoField>
<InfoField label="UID" variant="simple">
<InfoField label="Finding UID" variant="simple">
<CodeSnippet value={attributes.uid} />
</InfoField>
@@ -27,7 +27,7 @@ export const ColumnProviders: ColumnDef<ProviderProps>[] = [
{
accessorKey: "account",
header: ({ column }) => (
<DataTableColumnHeader column={column} title={"Account"} param="alias" />
<DataTableColumnHeader column={column} title={"Provider"} param="alias" />
),
cell: ({ row }) => {
const {
@@ -64,7 +64,11 @@ export const ColumnProviders: ColumnDef<ProviderProps>[] = [
{
accessorKey: "uid",
header: ({ column }) => (
<DataTableColumnHeader column={column} title={"Id"} param="uid" />
<DataTableColumnHeader
column={column}
title={"Provider UID"}
param="uid"
/>
),
cell: ({ row }) => {
const {