diff --git a/src/powershell/tests/Test-Assessment.35016.md b/src/powershell/tests/Test-Assessment.35016.md new file mode 100644 index 000000000..eb3674de7 --- /dev/null +++ b/src/powershell/tests/Test-Assessment.35016.md @@ -0,0 +1,17 @@ +When sensitivity labels are not mandatory, users can send unclassified emails, share unclassified files and documents, create unclassified sites and groups, and publish unclassified Power BI content without applying appropriate protection labels. This creates a significant security and compliance risk because threat actors can easily exfiltrate sensitive data without any classification metadata to indicate its sensitivity level or trigger automated protection policies. Mandatory labeling must be configured across all workloads (Outlook for emails, Teams for teamwork, SharePoint/Microsoft 365 Groups for sites and groups, and Power BI for analytics content) to ensure comprehensive coverage. If data loss prevention (DLP) policies rely on label detection to identify and block sensitive content, unclassified data bypasses these controls entirely. Additionally, users may accidentally share confidential information without realizing it lacks proper protection, and organizations lose audit trail visibility into what data is being handled and how. Without mandatory labeling across all platforms, compliance frameworks such as GDPR, HIPAA, or industry-specific regulations cannot be effectively enforced because sensitive data remains unidentified. Organizations should implement at least one sensitivity label policy with mandatory labeling enabled across Outlook, Teams/Teamwork, SharePoint/Sites and Groups, and Power BI to ensure all communications, documents, and analytics content are classified before sharing, enabling both automated protection mechanisms and complete audit visibility. + +**Remediation action** +1. Navigate to Sensitivity label policies in Microsoft Purview + - [Sensitivity label policies](https://purview.microsoft.com/informationprotection/labelpolicies) +2. Create or update a policy to enable mandatory labeling for target workloads (Outlook, Teams, SharePoint, Power BI) +3. Enable specific settings: + - "Require users to apply a label to their email" (Outlook) + - "Require users to apply a label for Teams, groups, and SharePoint content" (collaboration) + - Mandatory labeling for Power BI content +4. Set policy scope (global or specific groups) +5. Test with pilot users before organization-wide rollout + +**Learn More:** [Require users to apply a label](https://learn.microsoft.com/en-us/purview/sensitivity-labels-office-apps#require-users-to-apply-a-label-to-their-email-and-documents) + + +%TestResult% diff --git a/src/powershell/tests/Test-Assessment.35016.ps1 b/src/powershell/tests/Test-Assessment.35016.ps1 new file mode 100644 index 000000000..dfc248bc8 --- /dev/null +++ b/src/powershell/tests/Test-Assessment.35016.ps1 @@ -0,0 +1,255 @@ +<# +.SYNOPSIS + Mandatory Labeling Enabled for Sensitivity Labels +#> + +function Test-Assessment-35016 { + [ZtTest( + Category = 'Information Protection', + ImplementationCost = 'Medium', + MinimumLicense = ('Microsoft 365 E3'), + Pillar = 'Data', + RiskLevel = 'High', + SfiPillar = 'Protect tenants and production systems', + TenantType = ('Workforce','External'), + TestId = 35016, + Title = 'Mandatory labeling enabled for sensitivity labels', + UserImpact = 'High' + )] + [CmdletBinding()] + param() + + #region Data Collection + Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose + + $activity = 'Checking mandatory labeling configuration' + Write-ZtProgress -Activity $activity -Status 'Getting sensitivity label policies' + + $errorMsg = $null + $enabledPolicies = @() + + try { + # Q1: Retrieve all enabled sensitivity label policies to assess mandatory labeling configuration + $enabledPolicies = Get-LabelPolicy -ErrorAction Stop | Where-Object { $_.Enabled -eq $true } + } + catch { + $errorMsg = $_ + Write-PSFMessage "Error querying label policies: $_" -Level Error + } + #endregion Data Collection + + #region Assessment Logic + $allPolicySettings = @() + $mandatoryPolicies = @() + $xmlParseErrors = @() + $passed = $false + $customStatus = $null + + if ($errorMsg) { + $testResultMarkdown = "⚠️ Unable to determine mandatory labeling status due to error: $errorMsg`n`n" + $customStatus = 'Investigate' + } + else { + Write-PSFMessage "Found $($enabledPolicies.Count) enabled label policies" -Level Verbose + + try { + # Examine label policy settings for mandatory labeling + foreach ($policy in $enabledPolicies) { + # Determine policy scope: + # - Global if any location is set to "All" + # - Scoped if specific users/groups are defined + $allLocationNames = @( + $policy.ExchangeLocation.Name + $policy.ModernGroupLocation.Name + $policy.SharePointLocation.Name + $policy.OneDriveLocation.Name + $policy.SkypeLocation.Name + $policy.PublicFolderLocation.Name + ) | Where-Object { $_ } + + $isGlobal = $allLocationNames -contains 'All' + + $policySettings = @{ + PolicyName = $policy.Name + Guid = $policy.Guid + Enabled = $policy.Enabled + EmailMandatory = $false + TeamworkMandatory = $false + SiteGroupMandatory = $false + PowerBIMandatory = $false + EmailOverride = $false + Scope = if ($isGlobal) { 'Global' } else { 'Scoped' } + LabelsCount = $policy.Labels.Count + } + + # Parse PolicySettingsBlob XML for mandatory labeling flags + if (-not [string]::IsNullOrWhiteSpace($policy.PolicySettingsBlob)) { + try { + $xmlSettings = [xml]$policy.PolicySettingsBlob + + # Validate XML structure before accessing properties + if ($xmlSettings.settings -and $xmlSettings.settings.setting) { + # Access settings as XML elements for direct property lookup + foreach ($setting in $xmlSettings.settings.setting) { + # Add null safety for key and value attributes + if (-not $setting.key -or -not $setting.value) { + Write-PSFMessage "Skipping setting with null key or value in policy '$($policy.Name)'" -Level Verbose + continue + } + + $key = $setting.key.ToLower() + $value = $setting.value.ToLower() + + switch ($key) { + 'mandatory' { + $policySettings.EmailMandatory = ($value -eq 'true') + } + 'teamworkmandatory' { + $policySettings.TeamworkMandatory = ($value -eq 'true') + } + 'siteandgroupmandatory' { + $policySettings.SiteGroupMandatory = ($value -eq 'true') + } + 'powerbimandatory' { + $policySettings.PowerBIMandatory = ($value -eq 'true') + } + 'disablemandatoryinoutlook' { + $policySettings.EmailOverride = ($value -eq 'true') + } + default { + Write-PSFMessage "Unknown setting key '$key' in policy '$($policy.Name)'" -Level Verbose + } + } + } + } + else { + Write-PSFMessage "Policy '$($policy.Name)' has PolicySettingsBlob but no settings elements found" -Level Verbose + } + } + catch { + # Track parsing errors for reporting + $xmlParseErrors += [PSCustomObject]@{ + PolicyName = $policy.Name + Error = $_.Exception.Message + } + Write-PSFMessage "Error parsing PolicySettingsBlob XML for policy '$($policy.Name)': $_" -Level Warning + } + } + + # Per Microsoft documentation, disablemandatoryinoutlook can be set to explicitly + # disable mandatory labeling in Outlook even when the 'mandatory' setting is true. + # This provides an exception path for organizations that need mandatory labeling + # for files but not emails. Apply the override logic: + if ($policySettings.EmailMandatory -and $policySettings.EmailOverride) { + $policySettings.EmailMandatory = $false + } + + # Store all policy settings + $allPolicySettings += [PSCustomObject]$policySettings + + # Determine if this policy has ANY mandatory setting enabled (after applying overrides) + $hasMandatory = $policySettings.EmailMandatory -or + $policySettings.TeamworkMandatory -or + $policySettings.SiteGroupMandatory -or + $policySettings.PowerBIMandatory + + if ($hasMandatory) { + $mandatoryPolicies += [PSCustomObject]$policySettings + } + } + } + catch { + Write-PSFMessage "Error parsing label policy settings: $_" -Level Error + $testResultMarkdown = "⚠️ Unable to determine mandatory labeling status due to unexpected policy settings structure: $_`n`n" + $customStatus = 'Investigate' + } + + # Determine pass/fail status and message (only if no error occurred) + if ($null -eq $customStatus) { + if ($mandatoryPolicies.Count -gt 0) { + $passed = $true + $testResultMarkdown = "✅ Mandatory labeling is configured and enforced through at least one active sensitivity label policy across one or more workloads (Outlook, Teams/OneDrive, SharePoint/Microsoft 365 Groups, or Power BI).`n`n%TestResult%" + } + else { + $passed = $false + + if ($enabledPolicies.Count -eq 0) { + $testResultMarkdown = "❌ No enabled sensitivity label policies were found in your tenant.`n`n%TestResult%" + } + else { + $testResultMarkdown = "❌ No sensitivity label policies require users to apply labels across any workload (emails, files, sites, groups, or Power BI content).`n`n%TestResult%" + } + } + } + } + + #endregion Assessment Logic + + #region Report Generation + $mdInfo = '' + + # Show table whenever we have policy settings + if ($allPolicySettings.Count -gt 0) { + # Build policy table + $mdInfo += "`n`n### [Enabled label policies](https://purview.microsoft.com/informationprotection/labelpolicies)`n" + $mdInfo += "| Policy name | Email | Files/Collab | Sites/Groups | Power BI | Email override | Scope | Labels |`n" + $mdInfo += "| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |`n" + + foreach ($policy in $allPolicySettings) { + $policyName = Get-SafeMarkdown -Text $policy.PolicyName + $emailIcon = if ($policy.EmailMandatory) { '✅' } else { '❌' } + $teamworkIcon = if ($policy.TeamworkMandatory) { '✅' } else { '❌' } + $siteGroupIcon = if ($policy.SiteGroupMandatory) { '✅' } else { '❌' } + $powerBIIcon = if ($policy.PowerBIMandatory) { '✅' } else { '❌' } + $overrideIcon = if ($policy.EmailOverride) { 'Yes' } else { 'No' } + $mdInfo += "| $policyName | $emailIcon | $teamworkIcon | $siteGroupIcon | $powerBIIcon | $overrideIcon | $($policy.Scope) | $($policy.LabelsCount) |`n" + } + + # Build summary metrics + $emailCount = ($mandatoryPolicies | Where-Object { $_.EmailMandatory }).Count + $teamworkCount = ($mandatoryPolicies | Where-Object { $_.TeamworkMandatory }).Count + $siteGroupCount = ($mandatoryPolicies | Where-Object { $_.SiteGroupMandatory }).Count + $powerBICount = ($mandatoryPolicies | Where-Object { $_.PowerBIMandatory }).Count + + $mdInfo += "`n`n### Summary`n" + $mdInfo += "| Metric | Count |`n" + $mdInfo += "| :--- | :--- |`n" + $mdInfo += "| Total enabled label policies | $($allPolicySettings.Count) |`n" + $mdInfo += "| Total enabled label policies with mandatory labeling | $($mandatoryPolicies.Count) |`n" + $mdInfo += "| Email mandatory labeling | $emailCount |`n" + $mdInfo += "| File/collaboration mandatory labeling | $teamworkCount |`n" + $mdInfo += "| Site/group mandatory labeling | $siteGroupCount |`n" + $mdInfo += "| Power BI mandatory labeling | $powerBICount |" + } + + # Report XML parsing errors if any occurred + if ($xmlParseErrors.Count -gt 0) { + $mdInfo += "`n`n### ⚠️ XML Parsing Errors`n" + $mdInfo += "The following policies could not be parsed and were excluded from analysis:`n`n" + $mdInfo += "| Policy Name | Error |`n" + $mdInfo += "| :--- | :--- |`n" + foreach ($error in $xmlParseErrors) { + $errorMsg = Get-SafeMarkdown -Text $error.Error + $policyName = Get-SafeMarkdown -Text $error.PolicyName + $mdInfo += "| $policyName | $errorMsg |`n" + } + $mdInfo += "`n**Note**: These policies were treated as having no mandatory labeling configured.`n" + } + + $testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo + #endregion Report Generation + + $params = @{ + TestId = '35016' + Title = 'Mandatory labeling enabled for sensitivity labels' + Status = $passed + Result = $testResultMarkdown + } + + # Add CustomStatus if status is 'Investigate' + if ($null -ne $customStatus) { + $params.CustomStatus = $customStatus + } + + Add-ZtTestResultDetail @params +}