diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 41995eca6c..91d3e00e13 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -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 diff --git a/prowler/providers/m365/services/teams/teams_meeting_external_lobby_bypass_disabled/__init__.py b/prowler/providers/m365/services/teams/teams_meeting_external_lobby_bypass_disabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/m365/services/teams/teams_meeting_external_lobby_bypass_disabled/teams_meeting_external_lobby_bypass_disabled.metadata.json b/prowler/providers/m365/services/teams/teams_meeting_external_lobby_bypass_disabled/teams_meeting_external_lobby_bypass_disabled.metadata.json new file mode 100644 index 0000000000..7bbc9b0a54 --- /dev/null +++ b/prowler/providers/m365/services/teams/teams_meeting_external_lobby_bypass_disabled/teams_meeting_external_lobby_bypass_disabled.metadata.json @@ -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": "" +} diff --git a/prowler/providers/m365/services/teams/teams_meeting_external_lobby_bypass_disabled/teams_meeting_external_lobby_bypass_disabled.py b/prowler/providers/m365/services/teams/teams_meeting_external_lobby_bypass_disabled/teams_meeting_external_lobby_bypass_disabled.py new file mode 100644 index 0000000000..53796008f5 --- /dev/null +++ b/prowler/providers/m365/services/teams/teams_meeting_external_lobby_bypass_disabled/teams_meeting_external_lobby_bypass_disabled.py @@ -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 diff --git a/prowler/providers/m365/services/teams/teams_service.py b/prowler/providers/m365/services/teams/teams_service.py index 7231176b9a..2833142ee2 100644 --- a/prowler/providers/m365/services/teams/teams_service.py +++ b/prowler/providers/m365/services/teams/teams_service.py @@ -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): diff --git a/tests/providers/m365/services/teams/teams_meeting_external_lobby_bypass_disabled/teams_meeting_external_lobby_bypass_disabled_test.py b/tests/providers/m365/services/teams/teams_meeting_external_lobby_bypass_disabled/teams_meeting_external_lobby_bypass_disabled_test.py new file mode 100644 index 0000000000..8fe5d384d4 --- /dev/null +++ b/tests/providers/m365/services/teams/teams_meeting_external_lobby_bypass_disabled/teams_meeting_external_lobby_bypass_disabled_test.py @@ -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" diff --git a/tests/providers/m365/services/teams/teams_service_test.py b/tests/providers/m365/services/teams/teams_service_test.py index a4b523eaf7..d5aab163dc 100644 --- a/tests/providers/m365/services/teams/teams_service_test.py +++ b/tests/providers/m365/services/teams/teams_service_test.py @@ -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()