Private/BaselineValidation.ps1
|
function Test-HLParameterType { [CmdletBinding()] param( [AllowNull()] [object]$Value, [AllowNull()] [object]$Template ) if ($null -eq $Template) { return $true } if ($null -eq $Value) { return $false } if ($Template -is [bool]) { return $Value -is [bool] } if ($Template -is [string]) { return $Value -is [string] } if ($Template -is [byte] -or $Template -is [int16] -or $Template -is [int32] -or $Template -is [int64]) { return $Value -is [byte] -or $Value -is [int16] -or $Value -is [int32] -or $Value -is [int64] } if ($Template -is [single] -or $Template -is [double] -or $Template -is [decimal]) { return $Value -is [byte] -or $Value -is [int16] -or $Value -is [int32] -or $Value -is [int64] -or $Value -is [single] -or $Value -is [double] -or $Value -is [decimal] } if ($Template -is [System.Collections.IDictionary] -or $Template -is [pscustomobject]) { return $Value -is [System.Collections.IDictionary] -or $Value -is [pscustomobject] } if ($Template -is [System.Collections.IEnumerable] -and -not ($Template -is [string])) { return $Value -is [System.Collections.IEnumerable] -and -not ($Value -is [string]) } return $Value.GetType() -eq $Template.GetType() } function Assert-HLBaselineDocument { [CmdletBinding()] param( [Parameter(Mandatory)] [object]$Document, [Parameter(Mandatory)] [object]$Catalog, [string]$Source = 'baseline document' ) $allowedRoot = @('$schema', 'schemaVersion', 'name', 'displayName', 'version', 'description', 'extends', 'sourceBasis', 'supportedRoles', 'excludedControls', 'controls', 'notes') foreach ($property in @($Document.PSObject.Properties)) { if ($property.Name -notin $allowedRoot) { throw "$Source contains unsupported property '$($property.Name)'." } } foreach ($required in @('schemaVersion', 'name', 'displayName', 'version', 'description', 'controls')) { if (-not (Test-HLProperty -InputObject $Document -Name $required) -or $null -eq $Document.$required) { throw "$Source is missing required property '$required'." } } if ([string]$Document.schemaVersion -ne '1.0') { throw "$Source schemaVersion must be '1.0'." } if ([string]$Document.name -notmatch '^[A-Za-z][A-Za-z0-9_-]{1,63}$') { throw "$Source name must start with a letter and contain 2 to 64 letters, digits, underscores, or hyphens." } if ([string]$Document.version -notmatch '^\d+\.\d+\.\d+$') { throw "$Source version must use semantic versioning (for example 1.0.0)." } if (([string]$Document.displayName).Trim().Length -lt 3) { throw "$Source displayName must contain at least 3 characters." } if (([string]$Document.description).Trim().Length -lt 20) { throw "$Source description must contain at least 20 characters." } $builtinNames = @(Get-HLBuiltinBaselineName) if ((Test-HLProperty -InputObject $Document -Name 'extends') -and [string]$Document.extends -notin $builtinNames) { throw "$Source extends unsupported built-in baseline '$($Document.extends)'." } if (Test-HLProperty -InputObject $Document -Name 'supportedRoles') { foreach ($role in @($Document.supportedRoles)) { if ([string]$role -notin $builtinNames) { throw "$Source contains unsupported role '$role'." } } } $catalogById = @{} foreach ($catalogControl in @($Catalog.controls)) { $catalogById[[string]$catalogControl.id] = $catalogControl } $excludedSeen = @{} if (Test-HLProperty -InputObject $Document -Name 'excludedControls') { foreach ($excludedIdValue in @($Document.excludedControls)) { $excludedId = [string]$excludedIdValue if (-not $catalogById.ContainsKey($excludedId)) { throw "$Source excludes unknown control '$excludedId'." } if ($excludedSeen.ContainsKey($excludedId)) { throw "$Source contains duplicate excluded control '$excludedId'." } $excludedSeen[$excludedId] = $true } } $controls = @($Document.controls) if ($controls.Count -eq 0) { throw "$Source must contain at least one control entry." } $controlSeen = @{} $allowedSeverity = @('Critical', 'High', 'Medium', 'Low', 'Informational') foreach ($entry in $controls) { foreach ($property in @($entry.PSObject.Properties)) { if ($property.Name -notin @('id', 'enabled', 'severity', 'parameters')) { throw "$Source control entry contains unsupported property '$($property.Name)'." } } if (-not (Test-HLProperty -InputObject $entry -Name 'id') -or [string]::IsNullOrWhiteSpace([string]$entry.id)) { throw "$Source contains a control entry without an id." } $id = [string]$entry.id if (-not $catalogById.ContainsKey($id)) { throw "$Source references unknown control '$id'." } if ($controlSeen.ContainsKey($id)) { throw "$Source contains duplicate control '$id'." } $controlSeen[$id] = $true if ((Test-HLProperty -InputObject $entry -Name 'enabled') -and $entry.enabled -isnot [bool]) { throw "$Source control '$id' enabled must be a boolean." } if ((Test-HLProperty -InputObject $entry -Name 'severity') -and [string]$entry.severity -notin $allowedSeverity) { throw "$Source control '$id' has unsupported severity '$($entry.severity)'." } if (Test-HLProperty -InputObject $entry -Name 'parameters') { if ($entry.parameters -isnot [pscustomobject] -and $entry.parameters -isnot [System.Collections.IDictionary]) { throw "$Source control '$id' parameters must be an object." } $template = $catalogById[$id].parameters foreach ($parameter in @($entry.parameters.PSObject.Properties)) { if (-not (Test-HLProperty -InputObject $template -Name $parameter.Name)) { throw "$Source control '$id' contains unknown parameter '$($parameter.Name)'." } $expectedType = $template.PSObject.Properties[$parameter.Name].Value if (-not (Test-HLParameterType -Value $parameter.Value -Template $expectedType)) { throw "$Source control '$id' parameter '$($parameter.Name)' has an incompatible type." } } } } } |