|
| 1 | +<# |
| 2 | +.SYNOPSIS |
| 3 | + Validates that at least one encryption-enabled sensitivity label is configured. |
| 4 | +
|
| 5 | +.DESCRIPTION |
| 6 | + This test checks if encryption-enabled sensitivity labels exist by: |
| 7 | + 1. Retrieving all sensitivity labels with LabelActions |
| 8 | + 2. Parsing LabelActions JSON to identify encrypt actions |
| 9 | + 3. Analyzing encryption settings (type, permissions, co-authoring) |
| 10 | +
|
| 11 | +.NOTES |
| 12 | + Test ID: 35013 |
| 13 | + Category: Sensitivity Labels Configuration |
| 14 | + Required Module: ExchangeOnlineManagement v3.5.1+ |
| 15 | + Required Connection: Connect-IPPSSession |
| 16 | +#> |
| 17 | + |
| 18 | +function Test-Assessment-35013 { |
| 19 | + [ZtTest( |
| 20 | + Category = 'Sensitivity Labels Configuration', |
| 21 | + ImplementationCost = 'Medium', |
| 22 | + MinimumLicense = 'Microsoft 365 E3', |
| 23 | + Pillar = 'Data', |
| 24 | + RiskLevel = 'High', |
| 25 | + SfiPillar = 'Protect tenants and production systems', |
| 26 | + TenantType = ('Workforce', 'External'), |
| 27 | + TestId = 35013, |
| 28 | + Title = 'Encryption-Enabled Labels', |
| 29 | + UserImpact = 'High' |
| 30 | + )] |
| 31 | + [CmdletBinding()] |
| 32 | + param() |
| 33 | + |
| 34 | + #region Data Collection |
| 35 | + Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose |
| 36 | + $activity = 'Checking encryption-enabled sensitivity labels' |
| 37 | + Write-ZtProgress -Activity $activity -Status 'Querying sensitivity labels' |
| 38 | + |
| 39 | + $getCmdletFailed = $false |
| 40 | + $parsingFailed = $false |
| 41 | + $allLabels = $null |
| 42 | + $encryptedLabels = @() |
| 43 | + |
| 44 | + # Query: Get all sensitivity labels |
| 45 | + try { |
| 46 | + $allLabels = Get-Label -ErrorAction Stop |
| 47 | + |
| 48 | + # Parse LabelActions to extract encryption details |
| 49 | + foreach ($label in $allLabels) { |
| 50 | + try { |
| 51 | + $labelActions = $label.LabelActions | ConvertFrom-Json |
| 52 | + $encryptAction = $labelActions | Where-Object { $_.Type -eq 'encrypt' } |
| 53 | + |
| 54 | + if ($null -ne $encryptAction) { |
| 55 | + # Check if encryption is disabled |
| 56 | + $disabledSetting = $encryptAction.Settings | Where-Object { $_.Key -eq 'disabled' } |
| 57 | + if ($disabledSetting -and $disabledSetting.Value -eq 'true') { |
| 58 | + continue # Skip this label as encryption is disabled |
| 59 | + } |
| 60 | + |
| 61 | + # Check if DKE using Capabilities property (more reliable than LabelActions) |
| 62 | + $isDKE = $label.Capabilities -contains 'DoubleKeyEncryption' |
| 63 | + |
| 64 | + # Extract encryption details from Settings array (Key-Value pairs) |
| 65 | + $protectionTypeSetting = $encryptAction.Settings | Where-Object { $_.Key -eq 'protectiontype' } |
| 66 | + |
| 67 | + # Determine encryption type |
| 68 | + if ($isDKE) { |
| 69 | + $encryptionType = 'dke' |
| 70 | + } |
| 71 | + elseif ($protectionTypeSetting) { |
| 72 | + $encryptionType = $protectionTypeSetting.Value |
| 73 | + } |
| 74 | + else { |
| 75 | + $encryptionType = 'template' |
| 76 | + } |
| 77 | + |
| 78 | + $rightsDefSetting = $encryptAction.Settings | Where-Object { $_.Key -eq 'rightsdefinitions' } |
| 79 | + $rightsDef = if ($rightsDefSetting) { $rightsDefSetting.Value } else { 'Not specified' } |
| 80 | + |
| 81 | + $contentExpirySetting = $encryptAction.Settings | Where-Object { $_.Key -eq 'contentexpiredondateindaysornever' } |
| 82 | + $contentExpiry = if ($contentExpirySetting) { $contentExpirySetting.Value } else { 'Never' } |
| 83 | + |
| 84 | + # Determine if co-authoring is blocked |
| 85 | + $coAuthoringBlocked = ($encryptionType -eq 'dke') -or ($contentExpiry -ne 'Never') |
| 86 | + |
| 87 | + $encryptedLabels += [PSCustomObject]@{ |
| 88 | + Name = $label.DisplayName |
| 89 | + EncryptionType = $encryptionType |
| 90 | + RightsDefinitions = $rightsDef |
| 91 | + CoAuthoringBlocked = if ($coAuthoringBlocked) { 'Yes' } else { 'No' } |
| 92 | + } |
| 93 | + } |
| 94 | + } |
| 95 | + catch { |
| 96 | + Write-PSFMessage "Failed to parse LabelActions for label '$($label.DisplayName)': $_" -Tag Test -Level Warning |
| 97 | + $parsingFailed = $true |
| 98 | + } |
| 99 | + } |
| 100 | + } |
| 101 | + catch { |
| 102 | + $getCmdletFailed = $true |
| 103 | + Write-PSFMessage "Failed to retrieve sensitivity labels: $_" -Tag Test -Level Warning |
| 104 | + } |
| 105 | + #endregion Data Collection |
| 106 | + |
| 107 | + #region Assessment Logic |
| 108 | + $testResultMarkdown = '' |
| 109 | + $passed = $false |
| 110 | + $customStatus = $null |
| 111 | + |
| 112 | + # Check if Get-Label cmdlet failed |
| 113 | + if ($getCmdletFailed) { |
| 114 | + $testResultMarkdown = "⚠️ Unable to determine encryption-enabled label configuration due to query failure, connection issues, or insufficient permissions.`n`n%TestResult%" |
| 115 | + $passed = $false |
| 116 | + $customStatus = 'Investigate' |
| 117 | + } |
| 118 | + # Check if labels were retrieved but parsing failed |
| 119 | + elseif ($parsingFailed) { |
| 120 | + $testResultMarkdown = "⚠️ Labels exist but encryption configuration cannot be determined for some labels.`n`n%TestResult%" |
| 121 | + $passed = $false |
| 122 | + $customStatus = 'Investigate' |
| 123 | + } |
| 124 | + # Check encrypted label count |
| 125 | + elseif ($encryptedLabels.Count -eq 0) { |
| 126 | + $testResultMarkdown = "❌ No encryption-enabled labels exist; all labels provide classification only.`n`n%TestResult%" |
| 127 | + $passed = $false |
| 128 | + } |
| 129 | + else { |
| 130 | + $testResultMarkdown = "✅ At least one encryption-enabled sensitivity label is configured.`n`n%TestResult%" |
| 131 | + $passed = $true |
| 132 | + } |
| 133 | + #endregion Assessment Logic |
| 134 | + |
| 135 | + #region Report Generation |
| 136 | + $mdInfo = '' |
| 137 | + |
| 138 | + if ($encryptedLabels.Count -gt 0) { |
| 139 | + $formatTemplate = @' |
| 140 | +
|
| 141 | +## [{0}]({1}) |
| 142 | +
|
| 143 | +| Label name | Encryption type | Default permissions identities | Co-Authoring blocked | |
| 144 | +| :--------- | :-------------- | :----------------------------- | :------------------: | |
| 145 | +{2} |
| 146 | +
|
| 147 | +'@ |
| 148 | + |
| 149 | + $reportTitle = 'Encryption Label Details' |
| 150 | + $portalLink = 'https://purview.microsoft.com/informationprotection/informationprotectionlabels/sensitivitylabels' |
| 151 | + |
| 152 | + # Build table rows |
| 153 | + $labelDetails = '' |
| 154 | + foreach ($encLabel in $encryptedLabels) { |
| 155 | + $name = if ($encLabel.Name) { Get-SafeMarkdown -Text $encLabel.Name } else { 'N/A' } |
| 156 | + $encType = switch ($encLabel.EncryptionType) { |
| 157 | + 'template' { 'Standard RMS' } |
| 158 | + 'dke' { 'Double Key Encryption (DKE)' } |
| 159 | + 'userdefined' { 'User-Defined' } |
| 160 | + default { $encLabel.EncryptionType } |
| 161 | + } |
| 162 | + |
| 163 | + # Format rights definitions - show first 5 identities (users, groups, or domains) |
| 164 | + $rights = 'Not specified' |
| 165 | + if ($encLabel.RightsDefinitions -and $encLabel.RightsDefinitions -ne 'Not specified') { |
| 166 | + try { |
| 167 | + # Parse the JSON string containing rights definitions |
| 168 | + $rightsArray = $encLabel.RightsDefinitions | ConvertFrom-Json |
| 169 | + if ($rightsArray) { |
| 170 | + $identities = @($rightsArray | Where-Object { $_.Identity } | ForEach-Object { Get-SafeMarkdown -Text $_.Identity }) |
| 171 | + if ($identities.Count -gt 5) { |
| 172 | + $rights = ($identities[0..4] -join ', ') + ', ...' |
| 173 | + } |
| 174 | + else { |
| 175 | + $rights = $identities -join ', ' |
| 176 | + } |
| 177 | + } |
| 178 | + } |
| 179 | + catch { |
| 180 | + # If parsing fails, show fallback message |
| 181 | + $rights = 'Unable to parse permissions' |
| 182 | + } |
| 183 | + } |
| 184 | + |
| 185 | + $coAuthBlocked = $encLabel.CoAuthoringBlocked |
| 186 | + |
| 187 | + $labelDetails += "| $name | $encType | $rights | $coAuthBlocked |`n" |
| 188 | + } |
| 189 | + |
| 190 | + $labelDetails += "`n**Summary:**`n" |
| 191 | + $labelDetails += "* Total Encryption-Enabled Labels: $($encryptedLabels.Count)`n" |
| 192 | + |
| 193 | + # Count by encryption type |
| 194 | + $standardRMS = @($encryptedLabels | Where-Object { $_.EncryptionType -eq 'template' }).Count |
| 195 | + $userDefined = @($encryptedLabels | Where-Object { $_.EncryptionType -eq 'userdefined' }).Count |
| 196 | + $dkeLabels = @($encryptedLabels | Where-Object { $_.EncryptionType -eq 'dke' }).Count |
| 197 | + |
| 198 | + $labelDetails += "* Standard RMS: $standardRMS`n" |
| 199 | + $labelDetails += "* User-Defined: $userDefined`n" |
| 200 | + $labelDetails += "* Double Key Encryption (DKE): $dkeLabels" |
| 201 | + |
| 202 | + $mdInfo = $formatTemplate -f $reportTitle, $portalLink, $labelDetails |
| 203 | + } |
| 204 | + |
| 205 | + # Replace the placeholder with detailed information |
| 206 | + $testResultMarkdown = $testResultMarkdown -replace '%TestResult%', $mdInfo |
| 207 | + #endregion Report Generation |
| 208 | + |
| 209 | + $params = @{ |
| 210 | + TestId = '35013' |
| 211 | + Title = 'Encryption-Enabled Labels' |
| 212 | + Status = $passed |
| 213 | + Result = $testResultMarkdown |
| 214 | + } |
| 215 | + |
| 216 | + if ($null -ne $customStatus) { |
| 217 | + $params.CustomStatus = $customStatus |
| 218 | + } |
| 219 | + |
| 220 | + # Add test result details |
| 221 | + Add-ZtTestResultDetail @params |
| 222 | +} |
0 commit comments