Skip to content

Commit d8541cb

Browse files
authored
Merge pull request #1066 from KelvinTegelaar/dev
[pull] dev from KelvinTegelaar:dev
2 parents 82e1650 + 1390e14 commit d8541cb

5 files changed

Lines changed: 152 additions & 18 deletions

File tree

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# SAST for PowerShell: PSScriptAnalyzer with SARIF upload to the GitHub Security tab
2+
# ISO 27001:2022 A.8.28/8.29 evidence — CodeQL does not support PowerShell, so
3+
# PSScriptAnalyzer is the SAST tool for CIPP-API.
4+
name: SAST - PSScriptAnalyzer
5+
on:
6+
push:
7+
branches: [main, dev]
8+
pull_request:
9+
branches: [main, dev]
10+
schedule:
11+
- cron: "30 5 * * 1" # Weekly, catches newly added rules against unchanged code
12+
13+
permissions:
14+
contents: read
15+
security-events: write
16+
actions: read
17+
18+
jobs:
19+
psscriptanalyzer:
20+
if: github.repository_owner == 'KelvinTegelaar'
21+
name: Run PSScriptAnalyzer
22+
runs-on: ubuntu-latest
23+
steps:
24+
- name: Checkout repository
25+
uses: actions/checkout@v6
26+
27+
- name: Run PSScriptAnalyzer
28+
uses: microsoft/psscriptanalyzer-action@v1.1
29+
with:
30+
path: .\
31+
recurse: true
32+
# Security-focused rules; extend with full default ruleset if desired
33+
includeRule:
34+
'"PSAvoidUsingPlainTextForPassword",
35+
"PSAvoidUsingConvertToSecureStringWithPlainText",
36+
"PSAvoidUsingUsernameAndPasswordParams",
37+
"PSUsePSCredentialType",
38+
"PSAvoidUsingInvokeExpression",
39+
"PSAvoidGlobalVars",
40+
"PSAvoidUsingComputerNameHardcoded",
41+
"PSUseDeclaredVarsMoreThanAssignments"'
42+
output: results.sarif
43+
44+
- name: Upload SARIF to Security tab
45+
uses: github/codeql-action/upload-sarif@v4
46+
with:
47+
sarif_file: results.sarif

Modules/CIPPTests/Public/Tests/GenericTests/Identity/Invoke-CippTestGenericTest011.ps1

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,24 @@ function Invoke-CippTestGenericTest011 {
1313
return
1414
}
1515

16-
# Load standards.json for friendly name resolution
16+
# Load standards.json for friendly name and compliance-tag resolution
1717
$StandardsLabelMap = @{}
18+
$StandardsTagMap = @{}
1819
$StandardsJsonPath = Join-Path $env:CIPPRootPath 'Config\standards.json'
1920
if (Test-Path $StandardsJsonPath) {
2021
$StandardsJson = Get-Content $StandardsJsonPath -Raw | ConvertFrom-Json
2122
foreach ($Std in $StandardsJson) {
2223
if ($Std.name -and $Std.label) {
2324
$StandardsLabelMap[$Std.name] = $Std.label
2425
}
26+
if ($Std.name -and $Std.tag) {
27+
# Keep human-readable compliance-framework references (CIS, NIST, etc. - they
28+
# contain spaces) and drop internal single-token tags like 'mip_search_auditlog'.
29+
$FrameworkTags = @($Std.tag | Where-Object { $_ -is [string] -and $_ -match '\s' })
30+
if ($FrameworkTags.Count -gt 0) {
31+
$StandardsTagMap[$Std.name] = ($FrameworkTags -join ', ')
32+
}
33+
}
2534
}
2635
}
2736

@@ -130,6 +139,26 @@ function Invoke-CippTestGenericTest011 {
130139
return $null
131140
}
132141

142+
# Helper: resolve a standard's compliance-framework tags (CIS/NIST/etc.). Template-based
143+
# standards (Intune/CA/Quarantine) have no standards.json entry, so they return empty.
144+
$ResolveTags = {
145+
param($StandardName)
146+
if ([string]::IsNullOrWhiteSpace($StandardName)) { return '' }
147+
if ($StandardsTagMap.ContainsKey($StandardName)) { return $StandardsTagMap[$StandardName] }
148+
return ''
149+
}
150+
151+
# Helper: render a stored CurrentValue/ExpectedValue (plain string, bool, or compact JSON)
152+
# safely inside a markdown table cell - escape pipes, collapse newlines, and truncate blobs.
153+
$FormatValue = {
154+
param($Value)
155+
if ($null -eq $Value -or "$Value" -eq '') { return '' }
156+
$Text = [string]$Value
157+
$Text = $Text -replace '\|', '\|' -replace '\r?\n', ' '
158+
if ($Text.Length -gt 300) { $Text = $Text.Substring(0, 297) + '...' }
159+
return $Text
160+
}
161+
133162
foreach ($Template in $AlignmentItems) {
134163
$TemplateName = $Template.StandardName
135164
$Score = $Template.AlignmentScore
@@ -168,24 +197,32 @@ function Invoke-CippTestGenericTest011 {
168197

169198
# Compliant items
170199
if ($CompliantItems.Count -gt 0) {
171-
$null = $Result.Append("| Standard | Status |`n")
172-
$null = $Result.Append("|----------|--------|`n")
200+
$null = $Result.Append("#### Compliant Standards`n`n")
201+
$null = $Result.Append("| Standard | Tags | Status |`n")
202+
$null = $Result.Append("|----------|------|--------|`n")
173203
foreach ($Item in $CompliantItems) {
174204
$FriendlyName = & $ResolveDisplayName $Item.StandardName $TemplateSettings
175205
if (-not $FriendlyName) { continue }
176-
$null = $Result.Append("| $FriendlyName | ✅ Compliant |`n")
206+
$Tags = & $ResolveTags $Item.StandardName
207+
$null = $Result.Append("| $FriendlyName | $Tags | ✅ Compliant |`n")
177208
}
178209
$null = $Result.Append("`n")
179210
}
180211

181-
# Non-compliant items
212+
# Non-compliant items — include the compliance tags and the reason for failure
213+
# (expected value from the standard vs. the current config on the tenant).
182214
if ($NonCompliantItems.Count -gt 0) {
183-
$null = $Result.Append("| Standard | Status |`n")
184-
$null = $Result.Append("|----------|--------|`n")
215+
$null = $Result.Append("#### Non-Compliant Standards`n`n")
216+
$null = $Result.Append("| Standard | Tags | Expected Value | Current Value (on tenant) |`n")
217+
$null = $Result.Append("|----------|------|----------------|---------------------------|`n")
185218
foreach ($Item in $NonCompliantItems) {
186219
$FriendlyName = & $ResolveDisplayName $Item.StandardName $TemplateSettings
187220
if (-not $FriendlyName) { continue }
188-
$null = $Result.Append("| $FriendlyName | ❌ Non-Compliant |`n")
221+
$Tags = & $ResolveTags $Item.StandardName
222+
$Expected = & $FormatValue $Item.ExpectedValue
223+
$Current = & $FormatValue $Item.CurrentValue
224+
if (-not $Current) { $Current = '_Not configured / no data_' }
225+
$null = $Result.Append("| $FriendlyName | $Tags | $Expected | $Current |`n")
189226
}
190227
$null = $Result.Append("`n")
191228
}
@@ -194,25 +231,27 @@ function Invoke-CippTestGenericTest011 {
194231
if ($LicenseMissingItems.Count -gt 0) {
195232
$null = $Result.Append("#### Standards Not Applied Due to Missing Licenses`n`n")
196233
$null = $Result.Append("These items are part of this baseline, but your environment does not meet the minimum required licenses for them to be applied.`n`n")
197-
$null = $Result.Append("| Standard | Status |`n")
198-
$null = $Result.Append("|----------|--------|`n")
234+
$null = $Result.Append("| Standard | Tags | Status |`n")
235+
$null = $Result.Append("|----------|------|--------|`n")
199236
foreach ($Item in $LicenseMissingItems) {
200237
$FriendlyName = & $ResolveDisplayName $Item.StandardName $TemplateSettings
201238
if (-not $FriendlyName) { continue }
202-
$null = $Result.Append("| $FriendlyName | ⚠️ License Missing |`n")
239+
$Tags = & $ResolveTags $Item.StandardName
240+
$null = $Result.Append("| $FriendlyName | $Tags | ⚠️ License Missing |`n")
203241
}
204242
$null = $Result.Append("`n")
205243
}
206244

207245
# Reporting disabled items
208246
if ($ReportingDisabledItems.Count -gt 0) {
209247
$null = $Result.Append("#### Standards With Reporting Disabled`n`n")
210-
$null = $Result.Append("| Standard | Status |`n")
211-
$null = $Result.Append("|----------|--------|`n")
248+
$null = $Result.Append("| Standard | Tags | Status |`n")
249+
$null = $Result.Append("|----------|------|--------|`n")
212250
foreach ($Item in $ReportingDisabledItems) {
213251
$FriendlyName = & $ResolveDisplayName $Item.StandardName $TemplateSettings
214252
if (-not $FriendlyName) { continue }
215-
$null = $Result.Append("| $FriendlyName | ⏸️ Reporting Disabled |`n")
253+
$Tags = & $ResolveTags $Item.StandardName
254+
$null = $Result.Append("| $FriendlyName | $Tags | ⏸️ Reporting Disabled |`n")
216255
}
217256
$null = $Result.Append("`n")
218257
}

Modules/CippExtensions/Public/Hudu/Invoke-HuduExtensionSync.ps1

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ function Invoke-HuduExtensionSync {
3232
# Get Asset cache
3333
$HuduAssetCache = Get-CippTable -tablename 'CacheHuduAssets'
3434

35+
# Get Relations cache - Hudu's relations API has no per-company filter, so this is cached
36+
# globally and shared across every tenant's sync instead of pulling the full relations
37+
# table (30s+ on large instances) on every single sync run.
38+
$HuduRelationsCache = Get-CippTable -tablename 'CacheHuduRelations'
39+
$HuduRelationsCacheTTLMinutes = 15
40+
3541
# Import license mapping
3642
$LicTable = [System.IO.File]::ReadAllText((Join-Path $env:CIPPRootPath 'Config\ConversionTable.csv')) | ConvertFrom-Csv
3743

@@ -132,7 +138,49 @@ function Invoke-HuduExtensionSync {
132138
$ExcludeSerials = $DefaultSerials
133139
}
134140

135-
$HuduRelations = Get-HuduRelations
141+
$RelationsCacheMeta = Get-CIPPAzDataTableEntity @HuduRelationsCache -Filter "PartitionKey eq 'CacheMetadata' and RowKey eq 'LastRefresh'"
142+
$RelationsCacheAgeMinutes = if ($RelationsCacheMeta.LastRefresh) { ((Get-Date).ToUniversalTime() - [datetime]$RelationsCacheMeta.LastRefresh).TotalMinutes } else { $null }
143+
144+
if ($null -ne $RelationsCacheAgeMinutes -and $RelationsCacheAgeMinutes -lt $HuduRelationsCacheTTLMinutes) {
145+
$CachedRelationRows = Get-CIPPAzDataTableEntity @HuduRelationsCache -Filter "PartitionKey eq 'HuduRelation'"
146+
$HuduRelations = foreach ($CachedRelationRow in $CachedRelationRows) {
147+
[PSCustomObject]@{
148+
id = $CachedRelationRow.RowKey
149+
fromable_type = $CachedRelationRow.FromableType
150+
fromable_id = $CachedRelationRow.FromableId
151+
toable_type = $CachedRelationRow.ToableType
152+
toable_id = $CachedRelationRow.ToableId
153+
}
154+
}
155+
} else {
156+
$HuduRelations = Get-HuduRelations
157+
158+
$ExistingRelationRows = Get-CIPPAzDataTableEntity @HuduRelationsCache -Filter "PartitionKey eq 'HuduRelation'"
159+
if ($ExistingRelationRows) {
160+
Remove-AzDataTableEntity @HuduRelationsCache -Entity $ExistingRelationRows -Force
161+
}
162+
163+
$RelationEntities = foreach ($Relation in $HuduRelations) {
164+
[PSCustomObject]@{
165+
PartitionKey = 'HuduRelation'
166+
RowKey = [string]$Relation.id
167+
FromableType = [string]$Relation.fromable_type
168+
FromableId = [string]$Relation.fromable_id
169+
ToableType = [string]$Relation.toable_type
170+
ToableId = [string]$Relation.toable_id
171+
}
172+
}
173+
if ($RelationEntities) {
174+
Add-CIPPAzDataTableEntity @HuduRelationsCache -Entity $RelationEntities -Force
175+
}
176+
177+
$RelationsCacheMetaEntity = [PSCustomObject]@{
178+
PartitionKey = 'CacheMetadata'
179+
RowKey = 'LastRefresh'
180+
LastRefresh = (Get-Date).ToUniversalTime().ToString('o')
181+
}
182+
Add-CIPPAzDataTableEntity @HuduRelationsCache -Entity $RelationsCacheMetaEntity -Force
183+
}
136184
[System.Collections.Generic.List[object]]$Links = @(
137185
@{
138186
Title = 'M365 Admin Portal'

host.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
"distributedTracingEnabled": false,
1717
"version": "None"
1818
},
19-
"defaultVersion": "10.5.6",
19+
"defaultVersion": "10.5.7",
2020
"versionMatchStrategy": "Strict",
2121
"versionFailureStrategy": "Fail"
2222
}
2323
}
24-
}
24+
}

version_latest.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
10.5.6
1+
10.5.7

0 commit comments

Comments
 (0)