Skip to content

Commit 37a0918

Browse files
Merge pull request KelvinTegelaar#1893 from TecharyJames/feat/app-management-policy
Feat/app management policy
2 parents 151d61e + e89d305 commit 37a0918

1 file changed

Lines changed: 193 additions & 0 deletions

File tree

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
function Invoke-CIPPStandardAppManagementPolicy {
2+
<#
3+
.FUNCTIONALITY
4+
Internal
5+
.COMPONENT
6+
(APIName) AppManagementPolicy
7+
.SYNOPSIS
8+
(Label) Set Default App Management Policy
9+
.DESCRIPTION
10+
(Helptext) Configures the default app management policy to control application and service principal credential restrictions such as password and key credential lifetimes.
11+
(DocsDescription) Configures the default app management policy to control application and service principal credential restrictions. This includes password addition restrictions, custom password addition, symmetric key addition, and credential lifetime limits for both applications and service principals.
12+
.NOTES
13+
CAT
14+
Entra (AAD) Standards
15+
TAG
16+
EXECUTIVETEXT
17+
Enforces credential restrictions on application registrations and service principals to limit how secrets and certificates are created and how long they remain valid. This reduces the risk of long-lived or unmanaged credentials being used to access your tenant.
18+
ADDEDCOMPONENT
19+
{"type":"autoComplete","multiple":false,"creatable":false,"label":"Password Addition","name":"standards.AppManagementPolicy.passwordCredentialsPasswordAddition","options":[{"label":"Enabled","value":"enabled"},{"label":"Disabled","value":"disabled"}]}
20+
{"type":"autoComplete","multiple":false,"creatable":false,"label":"Custom Password","name":"standards.AppManagementPolicy.passwordCredentialsCustomPasswordAddition","options":[{"label":"Enabled","value":"enabled"},{"label":"Disabled","value":"disabled"}]}
21+
{"type":"number","label":"Password Credentials Max Lifetime (Days)","name":"standards.AppManagementPolicy.passwordCredentialsMaxLifetime"}
22+
{"type":"number","label":"Key Credentials Max Lifetime (Days)","name":"standards.AppManagementPolicy.keyCredentialsMaxLifetime"}
23+
IMPACT
24+
Medium Impact
25+
ADDEDDATE
26+
2026-03-13
27+
POWERSHELLEQUIVALENT
28+
Graph API
29+
RECOMMENDEDBY
30+
UPDATECOMMENTBLOCK
31+
Run the Tools\Update-StandardsComments.ps1 script to update this comment block
32+
.LINK
33+
https://docs.cipp.app/user-documentation/tenant/standards/list-standards
34+
#>
35+
36+
param($Tenant, $Settings)
37+
38+
# Get current app management policy
39+
try {
40+
$CurrentPolicy = New-GraphGetRequest -Uri 'https://graph.microsoft.com/v1.0/policies/defaultAppManagementPolicy' -tenantid $Tenant -AsApp $true
41+
} catch {
42+
$ErrorMessage = Get-CippException -Exception $_
43+
Write-LogMessage -API 'Standards' -tenant $Tenant -message "Failed to get App Management Policy. Error: $($ErrorMessage.NormalizedError)" -sev Error -LogData $ErrorMessage
44+
return
45+
}
46+
47+
# Unwrap autoComplete values - frontend sends {label, value} objects, extract the string
48+
$passwordAdditionState = [string]$Settings.passwordCredentialsPasswordAddition.value
49+
$customPasswordState = [string]$Settings.passwordCredentialsCustomPasswordAddition.value
50+
$passwordMaxLifetimeDays = $Settings.passwordCredentialsMaxLifetime
51+
$keyMaxLifetimeDays = $Settings.keyCredentialsMaxLifetime
52+
53+
# Convert user-entered days to ISO 8601 duration format (P<n>D)
54+
$passwordMaxLifetimeISO = if (-not [string]::IsNullOrWhiteSpace($passwordMaxLifetimeDays)) { "P${passwordMaxLifetimeDays}D" } else { $null }
55+
$keyMaxLifetimeISO = if (-not [string]::IsNullOrWhiteSpace($keyMaxLifetimeDays)) { "P${keyMaxLifetimeDays}D" } else { $null }
56+
57+
# Build desired password credential restrictions
58+
$desiredPasswordCredentials = [System.Collections.Generic.List[object]]::new()
59+
60+
# Password addition + symmetric key addition (mirrors password addition)
61+
if (-not [string]::IsNullOrWhiteSpace($passwordAdditionState)) {
62+
$desiredPasswordCredentials.Add([ordered]@{
63+
restrictionType = 'passwordAddition'
64+
state = $passwordAdditionState
65+
maxLifetime = $null
66+
restrictForAppsCreatedAfterDateTime = '0001-01-01T00:00:00Z'
67+
})
68+
$desiredPasswordCredentials.Add([ordered]@{
69+
restrictionType = 'symmetricKeyAddition'
70+
state = $passwordAdditionState
71+
maxLifetime = $null
72+
restrictForAppsCreatedAfterDateTime = '0001-01-01T00:00:00Z'
73+
})
74+
}
75+
76+
# Custom password
77+
if (-not [string]::IsNullOrWhiteSpace($customPasswordState)) {
78+
$desiredPasswordCredentials.Add([ordered]@{
79+
restrictionType = 'customPasswordAddition'
80+
state = $customPasswordState
81+
maxLifetime = $null
82+
restrictForAppsCreatedAfterDateTime = '0001-01-01T00:00:00Z'
83+
})
84+
}
85+
86+
# Password credential max lifetime
87+
if ($passwordMaxLifetimeISO) {
88+
$desiredPasswordCredentials.Add([ordered]@{
89+
restrictionType = 'passwordLifetime'
90+
state = 'enabled'
91+
maxLifetime = $passwordMaxLifetimeISO
92+
restrictForAppsCreatedAfterDateTime = '0001-01-01T00:00:00Z'
93+
})
94+
}
95+
96+
# Symmetric key credential max lifetime
97+
if ($keyMaxLifetimeISO) {
98+
$desiredPasswordCredentials.Add([ordered]@{
99+
restrictionType = 'symmetricKeyLifetime'
100+
state = 'enabled'
101+
maxLifetime = $keyMaxLifetimeISO
102+
restrictForAppsCreatedAfterDateTime = '0001-01-01T00:00:00Z'
103+
})
104+
}
105+
106+
# Key credentials (asymmetric key lifetime)
107+
$desiredKeyCredentials = @(
108+
if ($keyMaxLifetimeISO) {
109+
[ordered]@{
110+
restrictionType = 'asymmetricKeyLifetime'
111+
state = 'enabled'
112+
maxLifetime = $keyMaxLifetimeISO
113+
restrictForAppsCreatedAfterDateTime = '0001-01-01T00:00:00Z'
114+
}
115+
}
116+
)
117+
118+
if ($desiredPasswordCredentials.Count -eq 0 -and $desiredKeyCredentials.Count -eq 0) {
119+
Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'AppManagementPolicy: No valid restriction settings were configured.' -Sev Info
120+
return
121+
}
122+
123+
# Sort desired restrictions by restrictionType so order matches Graph API responses
124+
# Use script block because items are hashtables, not PSCustomObjects
125+
$sortedDesiredPasswordCredentials = @($desiredPasswordCredentials | Sort-Object { $_.restrictionType })
126+
$sortedDesiredKeyCredentials = @($desiredKeyCredentials | Sort-Object { $_.restrictionType })
127+
128+
$desiredState = [PSCustomObject]@{
129+
isEnabled = $true
130+
applicationRestrictions = [PSCustomObject]@{
131+
passwordCredentials = $sortedDesiredPasswordCredentials
132+
keyCredentials = $sortedDesiredKeyCredentials
133+
}
134+
servicePrincipalRestrictions = [PSCustomObject]@{
135+
passwordCredentials = $sortedDesiredPasswordCredentials
136+
keyCredentials = $sortedDesiredKeyCredentials
137+
}
138+
}
139+
140+
# Sort current policy arrays the same way for consistent comparison
141+
$CurrentValue = [PSCustomObject]@{
142+
isEnabled = [bool]$CurrentPolicy.isEnabled
143+
applicationRestrictions = [PSCustomObject]@{
144+
passwordCredentials = @($CurrentPolicy.applicationRestrictions.passwordCredentials | Sort-Object -Property restrictionType)
145+
keyCredentials = @($CurrentPolicy.applicationRestrictions.keyCredentials | Sort-Object -Property restrictionType)
146+
}
147+
servicePrincipalRestrictions = [PSCustomObject]@{
148+
passwordCredentials = @($CurrentPolicy.servicePrincipalRestrictions.passwordCredentials | Sort-Object -Property restrictionType)
149+
keyCredentials = @($CurrentPolicy.servicePrincipalRestrictions.keyCredentials | Sort-Object -Property restrictionType)
150+
}
151+
}
152+
153+
$CurrentJson = $CurrentValue | ConvertTo-Json -Depth 10 -Compress
154+
$ExpectedJson = $desiredState | ConvertTo-Json -Depth 10 -Compress
155+
$StateIsCorrect = $CurrentJson -eq $ExpectedJson
156+
157+
if ($Settings.remediate -eq $true) {
158+
if ($StateIsCorrect -eq $true) {
159+
Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'App Management Policy is already in the desired state.' -Sev Info
160+
} else {
161+
try {
162+
$GraphRequest = @{
163+
tenantID = $Tenant
164+
uri = 'https://graph.microsoft.com/v1.0/policies/defaultAppManagementPolicy'
165+
AsApp = $true
166+
Type = 'PATCH'
167+
ContentType = 'application/json; charset=utf-8'
168+
Body = $desiredState | ConvertTo-Json -Depth 20 -Compress
169+
}
170+
171+
$null = New-GraphPostRequest @GraphRequest
172+
Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'Updated default app management policy.' -Sev Info
173+
} catch {
174+
$ErrorMessage = Get-CippException -Exception $_
175+
Write-LogMessage -API 'Standards' -Tenant $Tenant -Message "Failed to update default app management policy. Error: $($ErrorMessage.NormalizedError)" -Sev Error -LogData $ErrorMessage
176+
}
177+
}
178+
}
179+
180+
if ($Settings.alert -eq $true) {
181+
if ($StateIsCorrect -eq $true) {
182+
Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'App Management Policy is configured correctly.' -Sev Info
183+
} else {
184+
Write-StandardsAlert -message 'App Management Policy is not configured correctly.' -object $CurrentValue -tenant $Tenant -standardName 'AppManagementPolicy' -standardId $Settings.standardId
185+
Write-LogMessage -API 'Standards' -Tenant $Tenant -Message 'App Management Policy is not configured correctly.' -Sev Info
186+
}
187+
}
188+
189+
if ($Settings.report -eq $true) {
190+
Set-CIPPStandardsCompareField -FieldName 'standards.AppManagementPolicy' -CurrentValue $CurrentValue -ExpectedValue $desiredState -TenantFilter $Tenant
191+
Add-CIPPBPAField -FieldName 'AppManagementPolicy' -FieldValue $StateIsCorrect -StoreAs bool -Tenant $Tenant
192+
}
193+
}

0 commit comments

Comments
 (0)