feat(teams): add new check teams_meeting_external_lobby_bypass_disabled (#7568)

Co-authored-by: Andoni A <14891798+andoniaf@users.noreply.github.com>
Co-authored-by: Sergio Garcia <hello@mistercloudsec.com>
This commit is contained in:
Hugo Pereira Brito
2025-04-24 18:13:42 +02:00
committed by Pepe Fagoaga
parent 87c4361559
commit d929e293b5
7 changed files with 295 additions and 0 deletions
+1
View File
@@ -28,6 +28,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- Add new check for SafeList not enabled in the Connection Filter Policy from service Defender in M365 [(#7492)](https://github.com/prowler-cloud/prowler/pull/7492)
- Add new check for DKIM enabled for service Defender in M365 [(#7485)](https://github.com/prowler-cloud/prowler/pull/7485)
- Add new check `teams_meeting_anonymous_user_start_disabled` [(#7567)](https://github.com/prowler-cloud/prowler/pull/7567)
- Add new check `teams_meeting_external_lobby_bypass_disabled` [(#7568)](https://github.com/prowler-cloud/prowler/pull/7568)
### Fixed
@@ -0,0 +1,30 @@
{
"Provider": "m365",
"CheckID": "teams_meeting_external_lobby_bypass_disabled",
"CheckTitle": "Ensure only people in the organization can bypass the lobby.",
"CheckType": [],
"ServiceName": "teams",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "critical",
"ResourceType": "Teams Global Meeting Policy",
"Description": "Ensure only people in the organization can bypass the lobby.",
"Risk": "Allowing external users or unauthenticated participants to bypass the lobby increases the risk of unauthorized access to sensitive meetings and potential disruptions. It may also lead to unscheduled meetings being initiated by external parties.",
"RelatedUrl": "https://learn.microsoft.com/en-us/powershell/module/teams/set-csteamsmeetingpolicy?view=teams-ps",
"Remediation": {
"Code": {
"CLI": "Set-CsTeamsMeetingPolicy -Identity Global -AutoAdmittedUsers 'EveryoneInCompanyExcludingGuests' ",
"NativeIaC": "",
"Other": "1. Navigate to Microsoft Teams admin center https://admin.teams.microsoft.com. 2. Click to expand Meetings select Meeting policies. 3. Click Global (Org-wide default). 4. Under meeting join & lobby set Who can bypass the lobby to People in my org.",
"Terraform": ""
},
"Recommendation": {
"Text": "Ensure that only people within the organization can bypass the lobby, requiring external users and dial-in participants to wait for approval from an organizer, co-organizer, or presenter. This helps secure sensitive meetings and prevents unauthorized access.",
"Url": "https://learn.microsoft.com/en-us/powershell/module/teams/set-csteamsmeetingpolicy?view=teams-ps"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,52 @@
from typing import List
from prowler.lib.check.models import Check, CheckReportM365
from prowler.providers.m365.services.teams.teams_client import teams_client
class teams_meeting_external_lobby_bypass_disabled(Check):
"""Check if only people in the organization can bypass the lobby.
Attributes:
metadata: Metadata associated with the check (inherited from Check).
"""
def execute(self) -> List[CheckReportM365]:
"""Execute the check for only people in the organization can bypass the lobby.
This method checks if only people in the organization can bypass the lobby.
Returns:
List[CheckReportM365]: A list of reports containing the result of the check.
"""
findings = []
global_meeting_policy = teams_client.global_meeting_policy
if global_meeting_policy:
report = CheckReportM365(
metadata=self.metadata(),
resource=global_meeting_policy if global_meeting_policy else {},
resource_name="Teams Meetings Global (Org-wide default) Policy",
resource_id="teamsMeetingsGlobalPolicy",
)
report.status = "FAIL"
report.status_extended = (
"People outside the organization can bypass the lobby."
)
allowed_bypass_settings = {
"EveryoneInCompanyExcludingGuests",
"OrganizerOnly",
"InvitedUsers",
}
if (
global_meeting_policy.allow_external_users_to_bypass_lobby
in allowed_bypass_settings
):
report.status = "PASS"
report.status_extended = (
"Only people in the organization can bypass the lobby."
)
findings.append(report)
return findings
@@ -52,6 +52,9 @@ class Teams(M365Service):
allow_anonymous_users_to_start_meeting=global_meeting_policy.get(
"AllowAnonymousUsersToStartMeeting", True
),
allow_external_users_to_bypass_lobby=global_meeting_policy.get(
"AutoAdmittedUsers", "Everyone"
),
)
except Exception as error:
logger.error(
@@ -95,6 +98,7 @@ class TeamsSettings(BaseModel):
class GlobalMeetingPolicy(BaseModel):
allow_anonymous_users_to_join_meeting: bool = True
allow_anonymous_users_to_start_meeting: bool = True
allow_external_users_to_bypass_lobby: str = "Everyone"
class UserSettings(BaseModel):
@@ -0,0 +1,206 @@
from unittest import mock
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
class Test_teams_meeting_external_lobby_bypass_disabled:
def test_no_global_meeting_policy(self):
teams_client = mock.MagicMock()
teams_client.global_meeting_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_microsoft_teams"
),
mock.patch(
"prowler.providers.m365.services.teams.teams_meeting_external_lobby_bypass_disabled.teams_meeting_external_lobby_bypass_disabled.teams_client",
new=teams_client,
),
):
from prowler.providers.m365.services.teams.teams_meeting_external_lobby_bypass_disabled.teams_meeting_external_lobby_bypass_disabled import (
teams_meeting_external_lobby_bypass_disabled,
)
check = teams_meeting_external_lobby_bypass_disabled()
result = check.execute()
assert len(result) == 0
def test_external_users_can_bypass_lobby(self):
teams_client = mock.MagicMock()
teams_client.audited_tenant = "audited_tenant"
teams_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_microsoft_teams"
),
mock.patch(
"prowler.providers.m365.services.teams.teams_meeting_external_lobby_bypass_disabled.teams_meeting_external_lobby_bypass_disabled.teams_client",
new=teams_client,
),
):
from prowler.providers.m365.services.teams.teams_meeting_external_lobby_bypass_disabled.teams_meeting_external_lobby_bypass_disabled import (
teams_meeting_external_lobby_bypass_disabled,
)
from prowler.providers.m365.services.teams.teams_service import (
GlobalMeetingPolicy,
)
teams_client.global_meeting_policy = GlobalMeetingPolicy(
allow_external_users_to_bypass_lobby="Everyone"
)
check = teams_meeting_external_lobby_bypass_disabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "People outside the organization can bypass the lobby."
)
assert result[0].resource == teams_client.global_meeting_policy.dict()
assert (
result[0].resource_name
== "Teams Meetings Global (Org-wide default) Policy"
)
assert result[0].resource_id == "teamsMeetingsGlobalPolicy"
def test_only_internal_users_can_bypass_lobby(self):
teams_client = mock.MagicMock()
teams_client.audited_tenant = "audited_tenant"
teams_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_microsoft_teams"
),
mock.patch(
"prowler.providers.m365.services.teams.teams_meeting_external_lobby_bypass_disabled.teams_meeting_external_lobby_bypass_disabled.teams_client",
new=teams_client,
),
):
from prowler.providers.m365.services.teams.teams_meeting_external_lobby_bypass_disabled.teams_meeting_external_lobby_bypass_disabled import (
teams_meeting_external_lobby_bypass_disabled,
)
from prowler.providers.m365.services.teams.teams_service import (
GlobalMeetingPolicy,
)
teams_client.global_meeting_policy = GlobalMeetingPolicy(
allow_external_users_to_bypass_lobby="EveryoneInCompanyExcludingGuests"
)
check = teams_meeting_external_lobby_bypass_disabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "Only people in the organization can bypass the lobby."
)
assert result[0].resource == teams_client.global_meeting_policy.dict()
assert (
result[0].resource_name
== "Teams Meetings Global (Org-wide default) Policy"
)
assert result[0].resource_id == "teamsMeetingsGlobalPolicy"
def test_organizer_only_can_bypass_lobby(self):
teams_client = mock.MagicMock()
teams_client.audited_tenant = "audited_tenant"
teams_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_microsoft_teams"
),
mock.patch(
"prowler.providers.m365.services.teams.teams_meeting_external_lobby_bypass_disabled.teams_meeting_external_lobby_bypass_disabled.teams_client",
new=teams_client,
),
):
from prowler.providers.m365.services.teams.teams_meeting_external_lobby_bypass_disabled.teams_meeting_external_lobby_bypass_disabled import (
teams_meeting_external_lobby_bypass_disabled,
)
from prowler.providers.m365.services.teams.teams_service import (
GlobalMeetingPolicy,
)
teams_client.global_meeting_policy = GlobalMeetingPolicy(
allow_external_users_to_bypass_lobby="OrganizerOnly"
)
check = teams_meeting_external_lobby_bypass_disabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "Only people in the organization can bypass the lobby."
)
assert result[0].resource == teams_client.global_meeting_policy.dict()
assert (
result[0].resource_name
== "Teams Meetings Global (Org-wide default) Policy"
)
assert result[0].resource_id == "teamsMeetingsGlobalPolicy"
def test_invited_users_can_bypass_lobby(self):
teams_client = mock.MagicMock()
teams_client.audited_tenant = "audited_tenant"
teams_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_microsoft_teams"
),
mock.patch(
"prowler.providers.m365.services.teams.teams_meeting_external_lobby_bypass_disabled.teams_meeting_external_lobby_bypass_disabled.teams_client",
new=teams_client,
),
):
from prowler.providers.m365.services.teams.teams_meeting_external_lobby_bypass_disabled.teams_meeting_external_lobby_bypass_disabled import (
teams_meeting_external_lobby_bypass_disabled,
)
from prowler.providers.m365.services.teams.teams_service import (
GlobalMeetingPolicy,
)
teams_client.global_meeting_policy = GlobalMeetingPolicy(
allow_external_users_to_bypass_lobby="InvitedUsers"
)
check = teams_meeting_external_lobby_bypass_disabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "Only people in the organization can bypass the lobby."
)
assert result[0].resource == teams_client.global_meeting_policy.dict()
assert (
result[0].resource_name
== "Teams Meetings Global (Org-wide default) Policy"
)
assert result[0].resource_id == "teamsMeetingsGlobalPolicy"
@@ -28,6 +28,7 @@ def mock_get_global_meeting_policy(_):
return GlobalMeetingPolicy(
allow_anonymous_users_to_join_meeting=False,
allow_anonymous_users_to_start_meeting=False,
allow_external_users_to_bypass_lobby="EveryoneInCompanyExcludingGuests",
)
@@ -122,5 +123,6 @@ class Test_Teams_Service:
assert teams_client.global_meeting_policy == GlobalMeetingPolicy(
allow_anonymous_users_to_join_meeting=False,
allow_anonymous_users_to_start_meeting=False,
allow_external_users_to_bypass_lobby="EveryoneInCompanyExcludingGuests",
)
teams_client.powershell.close()