Private/Settings.ps1
|
# Copyright (c) 2026 Broadcom. All Rights Reserved. # Broadcom Confidential. The term "Broadcom" refers to Broadcom Inc. # and/or its subsidiaries. # # ============================================================================= # # SOFTWARE LICENSE AGREEMENT # # Copyright (c) CA, Inc. All rights reserved. # # You are hereby granted a non-exclusive, worldwide, royalty-free license # under CA, Inc.'s copyrights to use, copy, modify, and distribute this # software in source code or binary form for use in connection with CA, Inc. # products. # # This copyright notice shall be included in all copies or substantial # portions of the software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. # # ============================================================================= #region Settings Management function Get-PatchScanSettings { <# .SYNOPSIS Load patch scan settings from file. .DESCRIPTION Loads settings from a JSON file. If no SettingsFile is provided, uses the default 'scan-settings.json' in the module root directory. .PARAMETER SettingsFile Path to settings file (absolute or relative to module root). Optional. .EXAMPLE $settings = Get-PatchScanSettings $settings = Get-PatchScanSettings -SettingsFile "custom-settings.json" .OUTPUTS [PSCustomObject] Settings object with properties: environments, findingsOutputDirectory, etc. .NOTES Returns a default settings structure when the file is absent — does not throw. Callers should check Environments array to detect a first-run state. #> [CmdletBinding()] [OutputType([PSCustomObject])] Param ( [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String]$SettingsFile ) $moduleRoot = Split-Path -Parent $PSScriptRoot $resolvedPath = if ([String]::IsNullOrWhiteSpace($SettingsFile)) { if ([String]::IsNullOrWhiteSpace($env:VcfPatchScannerBaseDirectory)) { $err = "$($Script:VCF_PATCH_SCANNER_ENV_VAR) is not set. Run Initialize-VcfPatchScanner before using the scanner." Write-LogMessage -Type ERROR -Message $err throw [System.InvalidOperationException]::new($err) } $baseTrimmed = $env:VcfPatchScannerBaseDirectory.Trim() $candidate = Join-Path -Path $baseTrimmed -ChildPath $Script:SCAN_CONFIG_DIR_NAME -AdditionalChildPath $Script:SCAN_SETTINGS_FILE_NAME if (-not (Test-Path -LiteralPath $candidate -PathType Leaf)) { $err = "Settings file not found at '$candidate'. Re-run Initialize-VcfPatchScanner to recreate it." Write-LogMessage -Type ERROR -Message $err throw [System.InvalidOperationException]::new($err) } $candidate } else { if ($SettingsFile -match '[/\\]\.\.[/\\]' -or $SettingsFile -match '[/\\]\.\.$') { throw [System.InvalidOperationException]::new("Settings file path contains invalid traversal sequences: $SettingsFile") } if ([System.IO.Path]::IsPathRooted($SettingsFile)) { $SettingsFile } else { Join-Path -Path $moduleRoot -ChildPath $SettingsFile } } if (-not (Test-Path -LiteralPath $resolvedPath -PathType Leaf)) { throw [System.IO.FileNotFoundException]::new("Settings file not found: $resolvedPath") } $fileInfo = Get-Item -LiteralPath $resolvedPath $maxSizeBytes = 5MB if ($fileInfo.Length -gt $maxSizeBytes) { throw [System.InvalidOperationException]::new("Settings file is too large ($([Math]::Round($fileInfo.Length / 1MB, 2)) MB) — the maximum is $($maxSizeBytes / 1MB) MB.") } try { $content = Get-Content -LiteralPath $resolvedPath -Raw -ErrorAction Stop $settings = ConvertFrom-Json -InputObject $content -Depth $Script:JSON_PARSE_MAX_DEPTH -ErrorAction Stop return $settings } catch { throw [System.InvalidOperationException]::new("Failed to load settings from $resolvedPath`: $($_.Exception.Message)", $_.Exception) } } function Set-PatchScanSettings { <# .SYNOPSIS Save patch scan settings to file. .DESCRIPTION Saves settings object to a JSON file. Creates the file if it doesn't exist. Overwrites existing file if it does. .PARAMETER Settings Settings object to save. .PARAMETER OutputPath Path where settings file should be written (absolute or relative to module root). Default: scan-settings.json in module root. .EXAMPLE $settings = New-PatchScanEnvironmentTemplate Set-PatchScanSettings -Settings $settings -OutputPath "custom-settings.json" .OUTPUTS None .NOTES Writes settings atomically via a temp file in the same directory followed by a rename. The temp file is removed on failure. Creates the output directory when absent. #> [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [ValidateNotNull()] [PSCustomObject]$Settings, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String]$OutputPath ) $moduleRoot = Split-Path -Parent $PSScriptRoot $resolvedPath = if ([String]::IsNullOrWhiteSpace($OutputPath)) { Join-Path -Path $moduleRoot -ChildPath "scan-settings.json" } else { if ([System.IO.Path]::IsPathRooted($OutputPath)) { $OutputPath } else { Join-Path -Path $moduleRoot -ChildPath $OutputPath } } $tempPath = $null try { $directory = [System.IO.Path]::GetDirectoryName($resolvedPath) if (-not (Test-Path -LiteralPath $directory -PathType Container)) { New-Item -ItemType Directory -Path $directory -Force | Out-Null } $json = $Settings | ConvertTo-Json -Depth $Script:JSON_SERIALIZE_DEPTH -ErrorAction Stop $tempPath = Join-Path -Path $directory -ChildPath "settings_$(New-Guid).tmp" # Atomic write: temp file in same directory + rename so readers never see a partial file. $utf8NoBom = [System.Text.UTF8Encoding]::new($false) [System.IO.File]::WriteAllText($tempPath, $json, $utf8NoBom) Move-Item -LiteralPath $tempPath -Destination $resolvedPath -Force $tempPath = $null Write-LogMessage -Type INFO -Message "Settings saved to $resolvedPath" } catch { if ($null -ne $tempPath -and (Test-Path -LiteralPath $tempPath -PathType Leaf)) { Remove-Item -LiteralPath $tempPath -Force -ErrorAction SilentlyContinue } throw [System.IO.IOException]::new("Failed to save settings to $resolvedPath`: $($_.Exception.Message)", $_.Exception) } } function New-PatchScanEnvironmentTemplate { <# .SYNOPSIS Generate a blank patch scan settings template. .DESCRIPTION Creates a template settings object with default values and empty environment array. Useful for programmatic settings creation or as a starting point for manual editing. .EXAMPLE $settings = New-PatchScanEnvironmentTemplate $settings.environments += @{ name = "prod"; type = "vcf9"; sddcManagerServer = "sddc.example.com"; ... } Set-PatchScanSettings -Settings $settings .OUTPUTS [PSCustomObject] Template settings object .NOTES Returns a template with placeholder strings. Callers must replace all placeholder values before passing to Set-PatchScanSettings. #> [CmdletBinding()] [OutputType([PSCustomObject])] Param () $template = [PSCustomObject]@{ environments = @() findingsOutputDirectory = "findings" logDirectory = "Logs" logLevel = "INFO" securityAdvisoryFile = "Data/securityAdvisory.json" ignoreCertificate = $true connectionTimeoutSeconds = 30 lightMode = $true sddcManagerServer = "" sddcManagerUser = "" vcfFMServer = "" vcfFMUser = "" vcfMajorVersion = "9" vcfOpsServer = "" vcfOpsUser = "" } return $template } function New-PatchScanEnvironment { <# .SYNOPSIS Create a new environment configuration object. .DESCRIPTION Builds a PSCustomObject representing a single scannable environment. The object is consumed by Invoke-VCFPatchScanner and can be persisted via Set-PatchScanSettings. Required parameters depend on the environment type: vcf9 requires VcfOpsServer and VcfOpsUser; vcf5 and vcf9 require SddcManagerServer and SddcManagerUser; vsphere8 and vvf9 require VcenterServer and VcenterUser. NsxManagerServer and NsxManagerUser are optional for vsphere8 and required for vvf9. VrslcmServer and VrslcmUser are optional for vcf5. .PARAMETER Name Display name for the environment. .PARAMETER Type Environment type: vcf5, vcf9, vsphere8, or vvf9. .PARAMETER SddcManagerServer SDDC Manager FQDN or IP (required for vcf5, vcf9). .PARAMETER SddcManagerInstanceName Human-readable VCF instance name (e.g. "San Francisco") discovered from VCF Operations. Stamped onto all VCF 9 inventory items so findings can be grouped by instance. VCF 9 only. .PARAMETER SddcManagerUser SDDC Manager username (required for vcf5, vcf9). .PARAMETER VcfOpsServer VCF Operations server FQDN or IP (VCF 9 only). .PARAMETER VcfOpsUser VCF Operations username (VCF 9 only). .PARAMETER VcfFMServer Fleet Manager / Ops Fleet Manager FQDN or IP (VCF 9 only). .PARAMETER VcfFMUser Fleet Manager / Ops Fleet Manager username (VCF 9 only). .PARAMETER VcenterServer vCenter FQDN or IP (vsphere8, vvf9 only). .PARAMETER VcenterUser vCenter username (vsphere8, vvf9 only). .EXAMPLE $env = New-PatchScanEnvironment -Name "Production" -Type vcf9 -SddcManagerServer "sddc.example.com" -SddcManagerUser "admin@vsphere.local" .OUTPUTS [PSCustomObject] Environment configuration .NOTES Validates required fields before appending. Throws [System.ArgumentException] on missing or duplicate environment name. #> [CmdletBinding()] [OutputType([PSCustomObject])] Param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String]$Name, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String]$NsxManagerServer, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String]$NsxManagerUser, [Parameter(Mandatory = $false)] [AllowEmptyString()] [ValidateNotNull()] [String]$SddcManagerInstanceName = '', [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String]$SddcManagerServer, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String]$SddcManagerUser, [Parameter(Mandatory = $true)] [ValidateSet('vcf5', 'vcf9', 'vsphere8', 'vvf9')] [String]$Type, [Parameter(Mandatory = $false)] [Switch]$UseSinglePassword, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String]$VcenterServer, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String]$VcenterUser, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String]$VcfFMServer, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String]$VcfFMUser, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String]$VcfOpsServer, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String]$VcfOpsUser, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String]$VrslcmServer, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [String]$VrslcmUser ) $env = [PSCustomObject]@{ name = $Name.Trim() type = $Type id = [System.Guid]::NewGuid().ToString() useSinglePassword = $UseSinglePassword.IsPresent } if (-not [String]::IsNullOrWhiteSpace($NsxManagerServer)) { $env | Add-Member -NotePropertyName nsxManagerServer -NotePropertyValue $NsxManagerServer.Trim() } if (-not [String]::IsNullOrWhiteSpace($NsxManagerUser)) { $env | Add-Member -NotePropertyName nsxManagerUser -NotePropertyValue $NsxManagerUser.Trim() } if (-not [String]::IsNullOrWhiteSpace($SddcManagerInstanceName)) { $env | Add-Member -NotePropertyName sddcManagerInstanceName -NotePropertyValue $SddcManagerInstanceName.Trim() } if (-not [String]::IsNullOrWhiteSpace($SddcManagerServer)) { $env | Add-Member -NotePropertyName sddcManagerServer -NotePropertyValue $SddcManagerServer.Trim() } if (-not [String]::IsNullOrWhiteSpace($SddcManagerUser)) { $env | Add-Member -NotePropertyName sddcManagerUser -NotePropertyValue $SddcManagerUser.Trim() } if (-not [String]::IsNullOrWhiteSpace($VcenterServer)) { $env | Add-Member -NotePropertyName vcenterServer -NotePropertyValue $VcenterServer.Trim() } if (-not [String]::IsNullOrWhiteSpace($VcenterUser)) { $env | Add-Member -NotePropertyName vcenterUser -NotePropertyValue $VcenterUser.Trim() } if (-not [String]::IsNullOrWhiteSpace($VcfFMServer)) { $env | Add-Member -NotePropertyName vcfFMServer -NotePropertyValue $VcfFMServer.Trim() } if (-not [String]::IsNullOrWhiteSpace($VcfFMUser)) { $env | Add-Member -NotePropertyName vcfFMUser -NotePropertyValue $VcfFMUser.Trim() } if (-not [String]::IsNullOrWhiteSpace($VcfOpsServer)) { $env | Add-Member -NotePropertyName vcfOpsServer -NotePropertyValue $VcfOpsServer.Trim() } if (-not [String]::IsNullOrWhiteSpace($VcfOpsUser)) { $env | Add-Member -NotePropertyName vcfOpsUser -NotePropertyValue $VcfOpsUser.Trim() } if (-not [String]::IsNullOrWhiteSpace($VrslcmServer)) { $env | Add-Member -NotePropertyName vrslcmServer -NotePropertyValue $VrslcmServer.Trim() } if (-not [String]::IsNullOrWhiteSpace($VrslcmUser)) { $env | Add-Member -NotePropertyName vrslcmUser -NotePropertyValue $VrslcmUser.Trim() } return $env } #endregion |