Checks/Teams/MET-Teams014-CrossTenantAccess.ps1
|
$graphAvailable = $true $graphErrorDetail = $null $defaultPolicy = $null $authPolicy = $null # This is the first MET check with a direct Microsoft Graph dependency (every prior Graph # call site lives in the private Expand-METGroupMembership helper, not a check body). There # is no Exchange Online or native Teams-module equivalent for Entra's cross-tenant access # default policy, so per CLAUDE.md's bar for a direct Graph dependency this is justified - # but it must still degrade non-fatally to NotApplicable rather than aborting the run, # mirroring Expand-METGroupMembership's try/catch pattern. try { if (-not (Get-Command -Name Get-MgPolicyCrossTenantAccessPolicyDefault -ErrorAction SilentlyContinue)) { throw 'Get-MgPolicyCrossTenantAccessPolicyDefault cmdlet not found - the Microsoft.Graph.Identity.SignIns module is not installed/imported or Microsoft Graph is not connected' } $defaultPolicy = Get-MgPolicyCrossTenantAccessPolicyDefault -ErrorAction Stop if (-not (Get-Command -Name Get-MgPolicyAuthorizationPolicy -ErrorAction SilentlyContinue)) { throw 'Get-MgPolicyAuthorizationPolicy cmdlet not found - the Microsoft.Graph.Identity.SignIns module is not installed/imported or Microsoft Graph is not connected' } $authPolicy = Get-MgPolicyAuthorizationPolicy -ErrorAction Stop } catch { $graphAvailable = $false $graphErrorDetail = $_.ToString() Write-Verbose "Cross-tenant access policy retrieval via Microsoft Graph failed: $_" } if (-not $graphAvailable) { New-METCheckResult -CheckId 'MET-Teams014' -Category Teams ` -Name 'Cross-Tenant Guest & External Collaboration Restrictions' ` -Result NotApplicable -Severity Medium ` -AffectedObject 'Cross-Tenant Access Policy' ` -Finding 'Microsoft Graph is unavailable, so the Entra ID cross-tenant access default policy and authorization policy could not be retrieved and this check could not run. This check requires Connect-METSession to have connected Microsoft Graph (i.e. run without -SkipGraph) and the Microsoft.Graph.Identity.SignIns module installed.' ` -Recommendation 'Run Connect-METSession without -SkipGraph, ensure the Microsoft.Graph.Identity.SignIns module (2.x) is installed, and re-run this check.' ` -ReferenceUrl 'https://learn.microsoft.com/en-us/graph/api/crosstenantaccesspolicy-get' ` -ErrorMessage $graphErrorDetail return } $findings = [System.Collections.Generic.List[string]]::new() $evaluated = $false $hasConcern = $false # --- Default cross-tenant access policy: inbound B2B collaboration/direct connect --- if ($null -ne $defaultPolicy) { $isServiceDefaultProp = $defaultPolicy.PSObject.Properties['IsServiceDefault'] if ($isServiceDefaultProp -and $isServiceDefaultProp.Value -eq $true) { $evaluated = $true $hasConcern = $true $findings.Add('The default cross-tenant access policy has not been customized (IsServiceDefault=true) - the tenant is relying on the Microsoft Entra system default, which permits inbound and outbound B2B collaboration with any external Microsoft Entra organization unless explicitly restricted') } foreach ($direction in @('B2BCollaborationInbound', 'B2BCollaborationOutbound', 'B2BDirectConnectInbound', 'B2BDirectConnectOutbound')) { $directionProp = $defaultPolicy.PSObject.Properties[$direction] if (-not $directionProp -or $null -eq $directionProp.Value) { continue } $setting = $directionProp.Value foreach ($scope in @('UsersAndGroups', 'Applications')) { $scopeProp = $setting.PSObject.Properties[$scope] if (-not $scopeProp -or $null -eq $scopeProp.Value) { continue } $scopeValue = $scopeProp.Value $accessTypeProp = $scopeValue.PSObject.Properties['AccessType'] if (-not $accessTypeProp -or $null -eq $accessTypeProp.Value) { continue } $evaluated = $true if ([string]$accessTypeProp.Value -eq 'allowed' -and $direction -like '*Inbound*') { $targetDescription = 'no explicit target restriction' $targetsProp = $scopeValue.PSObject.Properties['Targets'] if ($targetsProp -and $targetsProp.Value) { $targetNames = @($targetsProp.Value | ForEach-Object { $_.Target }) -join ', ' if ($targetNames) { $targetDescription = "targets: $targetNames" } } $hasConcern = $true $findings.Add("$direction ($scope) is set to Allowed ($targetDescription) - external users, groups, or applications from unconfigured/unknown external tenants can access your organization's resources via this path by default") } } } } # --- Authorization policy: who can invite guests --- if ($null -ne $authPolicy) { $allowInvitesProp = $authPolicy.PSObject.Properties['AllowInvitesFrom'] if ($allowInvitesProp -and $allowInvitesProp.Value) { $evaluated = $true if ([string]$allowInvitesProp.Value -eq 'everyone') { $hasConcern = $true $findings.Add("AllowInvitesFrom is set to 'everyone' - any user in the organization, including existing guests, can invite new external guests without administrator review") } } } if (-not $evaluated) { New-METCheckResult -CheckId 'MET-Teams014' -Category Teams ` -Name 'Cross-Tenant Guest & External Collaboration Restrictions' ` -Result Info -Severity Medium ` -AffectedObject 'Cross-Tenant Access Policy' ` -Finding 'Retrieved the default cross-tenant access policy and authorization policy from Microsoft Graph, but could not identify any recognizable settings (IsServiceDefault, B2B collaboration/direct connect inbound-outbound access type, or AllowInvitesFrom) to evaluate a Pass/Fail condition. The returned objects may be from an unexpected module version or shape - manual review is required.' ` -Recommendation 'Review the cross-tenant access default policy and authorization policy manually in the Entra admin center (entra.microsoft.com) > External Identities > Cross-tenant access settings.' ` -ReferenceUrl 'https://learn.microsoft.com/en-us/graph/api/crosstenantaccesspolicy-get' return } if ($hasConcern) { New-METCheckResult -CheckId 'MET-Teams014' -Category Teams ` -Name 'Cross-Tenant Guest & External Collaboration Restrictions' ` -Result Warning -Severity Medium ` -AffectedObject 'Cross-Tenant Access Policy' ` -Finding ($findings -join '; ') ` -Recommendation 'Review and scope the default cross-tenant access policy in the Entra admin center (entra.microsoft.com) > External Identities > Cross-tenant access settings > Default settings. Restrict inbound B2B collaboration/direct connect access to explicit organizations, users, or groups rather than relying on the open system default, and set AllowInvitesFrom to a more restrictive value (e.g. adminsAndGuestInviters) unless broad guest-invite rights are a deliberate business decision. Use Update-MgPolicyCrossTenantAccessPolicyDefault and Update-MgPolicyAuthorizationPolicy to remediate.' ` -ReferenceUrl 'https://learn.microsoft.com/en-us/graph/api/crosstenantaccesspolicy-get' } else { New-METCheckResult -CheckId 'MET-Teams014' -Category Teams ` -Name 'Cross-Tenant Guest & External Collaboration Restrictions' ` -Result Pass -Severity Medium ` -AffectedObject 'Cross-Tenant Access Policy' ` -Finding 'The default cross-tenant access policy is customized and inbound B2B collaboration/direct connect settings and guest-invite rights do not indicate open, unrestricted external collaboration' ` -ReferenceUrl 'https://learn.microsoft.com/en-us/graph/api/crosstenantaccesspolicy-get' } |