Skip to content

Commit b3f3b3c

Browse files
authored
Merge pull request #719 from alexandair/alex-35001
35001 - Add test for Conditional Access RMS exclusions
2 parents a1bb11d + 06f6bd9 commit b3f3b3c

File tree

3 files changed

+377
-0
lines changed

3 files changed

+377
-0
lines changed
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
Describe "Test-Assessment-35001" {
2+
BeforeAll {
3+
$here = $PSScriptRoot
4+
$srcRoot = Join-Path $here "../../src/powershell"
5+
6+
# Mock external module dependencies
7+
if (-not (Get-Command Write-PSFMessage -ErrorAction SilentlyContinue)) {
8+
function Write-PSFMessage {
9+
}
10+
}
11+
12+
# Load the class
13+
$classPath = Join-Path $srcRoot "classes/ZtTest.ps1"
14+
if (-not ("ZtTest" -as [type])) {
15+
. $classPath
16+
}
17+
18+
# Load the SUT
19+
$sut = Join-Path $srcRoot "tests/Test-Assessment.35001.ps1"
20+
. $sut
21+
22+
# Setup output file
23+
$script:outputFile = Join-Path $here "../TestResults/Report-Test-Assessment.35001.md"
24+
$outputDir = Split-Path $script:outputFile
25+
if (-not (Test-Path $outputDir)) {
26+
New-Item -ItemType Directory -Path $outputDir | Out-Null
27+
}
28+
"# Test Results for 35001`n" | Set-Content $script:outputFile
29+
}
30+
31+
BeforeEach {
32+
Mock Write-PSFMessage {}
33+
Mock Write-ZtProgress {}
34+
Mock Get-SafeMarkdown { param($Text) return $Text }
35+
}
36+
37+
Context "When no policies exist" {
38+
It "Should pass" {
39+
Mock Get-ZtConditionalAccessPolicy { return @() }
40+
41+
Mock Add-ZtTestResultDetail {
42+
param($TestId, $Title, $Status, $Result)
43+
"## Scenario: No policies`n`n$Result`n" | Add-Content $script:outputFile
44+
}
45+
46+
Test-Assessment-35001
47+
48+
Should -Invoke Add-ZtTestResultDetail -ParameterFilter {
49+
$Status -eq $true -and $Result -match "Microsoft Rights Management Service \(RMS\) is excluded"
50+
}
51+
}
52+
}
53+
54+
Context "When policies exist but RMS is excluded" {
55+
It "Should pass when 'All' apps included but RMS excluded" {
56+
Mock Get-ZtConditionalAccessPolicy {
57+
return @(
58+
[PSCustomObject]@{
59+
id = "policy-1"
60+
displayName = "Policy 1"
61+
state = "enabled"
62+
conditions = [PSCustomObject]@{
63+
applications = [PSCustomObject]@{
64+
includeApplications = @("All")
65+
excludeApplications = @("00000012-0000-0000-c000-000000000000")
66+
}
67+
}
68+
}
69+
)
70+
}
71+
72+
Mock Add-ZtTestResultDetail {
73+
param($TestId, $Title, $Status, $Result)
74+
"## Scenario: All apps included, RMS excluded`n`n$Result`n" | Add-Content $script:outputFile
75+
}
76+
77+
Test-Assessment-35001
78+
79+
Should -Invoke Add-ZtTestResultDetail -ParameterFilter {
80+
$Status -eq $true
81+
}
82+
}
83+
}
84+
85+
Context "When policies block RMS" {
86+
It "Should fail when 'All' apps included and RMS NOT excluded" {
87+
Mock Get-ZtConditionalAccessPolicy {
88+
return @(
89+
[PSCustomObject]@{
90+
id = "policy-block-all"
91+
displayName = "Block All Policy"
92+
state = "enabled"
93+
conditions = [PSCustomObject]@{
94+
applications = [PSCustomObject]@{
95+
includeApplications = @("All")
96+
excludeApplications = @("some-other-app-id")
97+
}
98+
}
99+
grantControls = [PSCustomObject]@{
100+
builtInControls = @("mfa")
101+
}
102+
sessionControls = [PSCustomObject]@{
103+
signInFrequency = [PSCustomObject]@{
104+
isEnabled = $true
105+
value = 4
106+
type = "hours"
107+
}
108+
}
109+
}
110+
)
111+
}
112+
113+
$script:capturedResult = $null
114+
Mock Add-ZtTestResultDetail {
115+
param($TestId, $Title, $Status, $Result)
116+
$script:capturedResult = $Result
117+
"## Scenario: Block All Policy`n`n$Result`n" | Add-Content $script:outputFile
118+
}
119+
120+
Test-Assessment-35001
121+
122+
Should -Invoke Add-ZtTestResultDetail -ParameterFilter {
123+
$Status -eq $false
124+
}
125+
$script:capturedResult | Should -Match "Block All Policy"
126+
$script:capturedResult | Should -Match "mfa"
127+
$script:capturedResult | Should -Match "Sign-in Frequency"
128+
}
129+
130+
It "Should fail when RMS explicitly included and NOT excluded" {
131+
Mock Get-ZtConditionalAccessPolicy {
132+
return @(
133+
[PSCustomObject]@{
134+
id = "policy-block-rms"
135+
displayName = "Block RMS Policy"
136+
state = "enabled"
137+
conditions = [PSCustomObject]@{
138+
applications = [PSCustomObject]@{
139+
includeApplications = @("00000012-0000-0000-c000-000000000000")
140+
excludeApplications = @()
141+
}
142+
}
143+
}
144+
)
145+
}
146+
147+
$script:capturedResult = $null
148+
Mock Add-ZtTestResultDetail {
149+
param($TestId, $Title, $Status, $Result)
150+
$script:capturedResult = $Result
151+
"## Scenario: Block RMS Policy`n`n$Result`n" | Add-Content $script:outputFile
152+
}
153+
154+
Test-Assessment-35001
155+
156+
Should -Invoke Add-ZtTestResultDetail -ParameterFilter {
157+
$Status -eq $false
158+
}
159+
$script:capturedResult | Should -Match "None"
160+
}
161+
}
162+
163+
Context "Error Handling" {
164+
It "Should handle errors gracefully" {
165+
Mock Get-ZtConditionalAccessPolicy { throw "Graph API Error" }
166+
167+
$script:capturedResult = $null
168+
Mock Add-ZtTestResultDetail {
169+
param($TestId, $Title, $Status, $Result)
170+
$script:capturedResult = $Result
171+
"## Scenario: Error Handling`n`n$Result`n" | Add-Content $script:outputFile
172+
}
173+
174+
Test-Assessment-35001
175+
176+
Should -Invoke Add-ZtTestResultDetail -ParameterFilter {
177+
$Status -eq $false
178+
}
179+
$script:capturedResult | Should -Match "Unable to determine RMS exclusion status"
180+
}
181+
}
182+
183+
Context "When policies are disabled" {
184+
It "Should pass when a blocking policy is disabled" {
185+
Mock Get-ZtConditionalAccessPolicy {
186+
return @(
187+
[PSCustomObject]@{
188+
id = "policy-disabled-block"
189+
displayName = "Disabled Block Policy"
190+
state = "disabled"
191+
conditions = [PSCustomObject]@{
192+
applications = [PSCustomObject]@{
193+
includeApplications = @("00000012-0000-0000-c000-000000000000")
194+
excludeApplications = @()
195+
}
196+
}
197+
}
198+
)
199+
}
200+
201+
Mock Add-ZtTestResultDetail {
202+
param($TestId, $Title, $Status, $Result)
203+
"## Scenario: Disabled Policy`n`n$Result`n" | Add-Content $script:outputFile
204+
}
205+
206+
Test-Assessment-35001
207+
208+
Should -Invoke Add-ZtTestResultDetail -ParameterFilter {
209+
$Status -eq $true
210+
}
211+
}
212+
}
213+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Microsoft Rights Management Service (RMS) is the protection technology that enforces encryption for sensitivity labels and information protection policies. When users access encrypted content, their applications must authenticate to the RMS service (App ID: `00000012-0000-0000-c000-000000000000`) to decrypt the content. If Conditional Access policies incorrectly block or restrict this authentication - for example, by requiring multi-factor authentication (MFA), device compliance, or specific network locations - users will be unable to open encrypted emails, documents, or files protected by sensitivity labels.
2+
This is most notable when trying to collaborate on MIP protected content from an external tenant to the source tenant.
3+
The RMS service should be explicitly excluded from Conditional Access policies that enforce authentication controls, as the application itself is handling the decryption and the user has already authenticated through their primary client application. Blocking RMS authentication prevents the decryption process and breaks information protection workflows across Microsoft 365 services including Outlook, Word, Excel, PowerPoint, Teams, and SharePoint.
4+
5+
**Remediation action**
6+
7+
To exclude RMS from Conditional Access policies:
8+
1. Navigate to [Microsoft Entra admin center > Entra ID > Conditional Access > Policies](https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/ConditionalAccessBlade/~/Policies)
9+
2. Select the policy that is blocking RMS
10+
3. Under Target resources > All resources (formerly 'All cloud apps')
11+
4. Under Exclude, select 'Select resources' and add "Microsoft Rights Management Services" (App ID: `00000012-0000-0000-c000-000000000000`)
12+
5. Save the policy
13+
14+
- [Microsoft Entra configuration for Azure Information Protection](https://learn.microsoft.com/purview/encryption-azure-ad-configuration)
15+
- [Conditional Access policies and encrypted documents](https://learn.microsoft.com/purview/encryption-azure-ad-configuration#conditional-access-policies-and-encrypted-documents)
16+
- [Conditional Access: Cloud apps, actions, and authentication context](https://learn.microsoft.com/entra/identity/conditional-access/concept-conditional-access-cloud-apps)
17+
18+
<!--- Results --->
19+
%TestResult%
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<#
2+
.SYNOPSIS
3+
Conditional Access RMS Exclusions
4+
#>
5+
6+
function Test-Assessment-35001 {
7+
[ZtTest(
8+
Category = 'Entra',
9+
ImplementationCost = 'Low',
10+
MinimumLicense = ('Microsoft 365 E5'),
11+
Pillar = 'Data',
12+
RiskLevel = 'High',
13+
SfiPillar = '',
14+
TenantType = ('Workforce','External'),
15+
TestId = 35001,
16+
Title = 'Conditional Access RMS Exclusions',
17+
UserImpact = 'Low'
18+
)]
19+
[CmdletBinding()]
20+
param()
21+
22+
#region Data Collection
23+
Write-PSFMessage '🟦 Start' -Tag Test -Level VeryVerbose
24+
25+
$activity = 'Checking Conditional Access RMS Exclusions'
26+
Write-ZtProgress -Activity $activity -Status 'Getting Conditional Access policies'
27+
28+
$rmsAppId = '00000012-0000-0000-c000-000000000000'
29+
$blockingPolicies = @()
30+
$policies = @()
31+
$errorMsg = $null
32+
33+
try {
34+
# Query: Get all enabled Conditional Access policies
35+
$policies = Get-ZtConditionalAccessPolicy | Where-Object { $_.state -eq 'enabled' }
36+
}
37+
catch {
38+
$errorMsg = $_
39+
Write-PSFMessage "Error querying Conditional Access policies: $_" -Level Error
40+
}
41+
#endregion Data Collection
42+
43+
#region Assessment Logic
44+
if ($errorMsg) {
45+
$passed = $false
46+
}
47+
else {
48+
foreach ($policy in $policies) {
49+
$includedApps = $policy.conditions.applications.includeApplications
50+
$excludedApps = $policy.conditions.applications.excludeApplications
51+
52+
$isRmsIncluded = ($includedApps -contains 'All') -or ($includedApps -contains $rmsAppId)
53+
$isRmsExcluded = $excludedApps -contains $rmsAppId
54+
55+
if ($isRmsIncluded -and -not $isRmsExcluded) {
56+
$blockingPolicies += $policy
57+
}
58+
}
59+
60+
$passed = $blockingPolicies.Count -eq 0
61+
}
62+
#endregion Assessment Logic
63+
64+
#region Report Generation
65+
if ($errorMsg) {
66+
$testResultMarkdown = "❌ Unable to determine RMS exclusion status due to error: $errorMsg"
67+
}
68+
elseif ($passed) {
69+
$testResultMarkdown = "✅ Microsoft Rights Management Service (RMS) is excluded from Conditional Access policies that enforce authentication controls."
70+
}
71+
else {
72+
$testResultMarkdown = "❌ Microsoft Rights Management Service (RMS) is blocked or restricted by one or more Conditional Access policies.`n`n"
73+
$testResultMarkdown += "**Policies Affecting RMS:**`n`n"
74+
$testResultMarkdown += "| Policy Name | State | RMS Targeted | RMS Excluded | Grant Controls | Session Controls |`n"
75+
$testResultMarkdown += "| :--- | :--- | :--- | :--- | :--- | :--- |`n"
76+
77+
foreach ($policy in $blockingPolicies) {
78+
$policyLink = "https://entra.microsoft.com/#view/Microsoft_AAD_ConditionalAccess/PolicyBlade/policyId/$($policy.id)"
79+
80+
# Grant Controls
81+
$grantControls = @()
82+
if ($policy.grantControls) {
83+
if ($policy.grantControls.builtInControls) { $grantControls += $policy.grantControls.builtInControls }
84+
if ($policy.grantControls.termsOfUse) { $grantControls += "Terms of Use" }
85+
}
86+
$grantDisplay = if ($grantControls.Count -gt 0) { $grantControls -join ', ' } else { 'None' }
87+
88+
# Session Controls
89+
$sessionControls = @()
90+
if ($policy.sessionControls) {
91+
foreach ($prop in $policy.sessionControls.PSObject.Properties) {
92+
$name = $prop.Name
93+
$value = $prop.Value
94+
95+
if ($null -eq $value) { continue }
96+
97+
$isSet = $false
98+
if ($value -is [bool]) {
99+
$isSet = $value
100+
}
101+
else {
102+
if ($value.PSObject.Properties.Match('isEnabled')) {
103+
$isSet = $value.isEnabled
104+
}
105+
else {
106+
$isSet = $true
107+
}
108+
}
109+
110+
if ($isSet) {
111+
$displayName = $name -replace '([a-z])([A-Z])', '$1 $2'
112+
$displayName = $displayName.Substring(0,1).ToUpper() + $displayName.Substring(1)
113+
114+
switch ($name) {
115+
'disableResilienceDefaults' { $displayName = 'Disable Resilience Defaults' }
116+
'cloudAppSecurity' { $displayName = 'Cloud App Security' }
117+
'signInFrequency' { $displayName = 'Sign-in Frequency' }
118+
'persistentBrowser' { $displayName = 'Persistent Browser' }
119+
'continuousAccessEvaluation' { $displayName = 'Customize Continuous Access Evaluation' }
120+
'globalSecureAccessFilteringProfile' { $displayName = 'Global Secure Access Security Profile' }
121+
'secureSignInSession' { $displayName = 'Secure Sign-in Session' }
122+
'applicationEnforcedRestrictions' { $displayName = 'App Enforced Restrictions' }
123+
'networkAccessSecurity' { $displayName = 'Network Access Security' }
124+
}
125+
$sessionControls += $displayName
126+
}
127+
}
128+
}
129+
$sessionDisplay = if ($sessionControls.Count -gt 0) { $sessionControls -join ', ' } else { 'None' }
130+
131+
$policyName = Get-SafeMarkdown -Text $policy.displayName
132+
133+
$testResultMarkdown += "| [$policyName]($policyLink) | $($policy.state) | Yes | No | $grantDisplay | $sessionDisplay |`n"
134+
}
135+
}
136+
#endregion Report Generation
137+
138+
$testResultDetail = @{
139+
TestId = '35001'
140+
Title = 'Conditional Access RMS Exclusions'
141+
Status = $passed
142+
Result = $testResultMarkdown
143+
}
144+
Add-ZtTestResultDetail @testResultDetail
145+
}

0 commit comments

Comments
 (0)