Private/Parse-CISYAML.ps1
|
<#
.SYNOPSIS Parses CIS YAML files and extracts registry/security policy settings. .DESCRIPTION Reads CIS benchmark YAML files and extracts all registry settings, security policies, and user rights assignments. #> function Parse-CISYAML { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$YAMLPath, [Parameter(Mandatory = $false)] [ValidateSet("Level1", "Level2", "All")] [string]$Level = "All" ) $results = @() if (-not (Test-Path $YAMLPath)) { Write-Warning "YAML file not found: $YAMLPath" return $results } $content = Get-Content $YAMLPath -Raw # Extract control ID from name (e.g., "1.1.1 | PATCH | ...") $controlIdMatch = [regex]::Match($content, '(\d+\.\d+(?:\.\d+)?(?:\.\d+)?)\s*\|\s*PATCH') $controlId = if ($controlIdMatch.Success) { $controlIdMatch.Groups[1].Value } else { "Unknown" } # Extract level tags $hasLevel1 = $content -match 'level1-corporate-enterprise-environment' $hasLevel2 = $content -match 'level2-high-security-sensitive-data-environment' # Filter by level if ($Level -eq "Level1" -and -not $hasLevel1) { return $results } if ($Level -eq "Level2" -and -not $hasLevel2) { return $results } # Extract registry settings (win_regedit) - improved line-by-line parsing $lines = $content -split "`r?`n" for ($i = 0; $i -lt $lines.Count; $i++) { if ($lines[$i] -match 'ansible\.windows\.win_regedit:') { # Found regedit block, extract next 4 lines (path, name, data, type) $registryPath = $null $registryName = $null $registryValue = $null $registryType = $null # Look ahead for path, name, data, type (within next 15 lines) for ($j = $i + 1; $j -lt [Math]::Min($i + 15, $lines.Count); $j++) { $testLine = $lines[$j] # Check if we've left the regedit block if ($testLine -match '^\s{0,2}(when|tags|loop|notify):' -or $testLine -match '^\s*-\s+name:') { break # End of regedit block } # Match path - extract everything after "path:" if ($testLine.Contains('path:')) { $pathIndex = $testLine.IndexOf('path:') if ($pathIndex -ge 0) { $pathValue = $testLine.Substring($pathIndex + 5).Trim() # Direct assignment - pathValue already contains the full path $registryPath = $pathValue } } # Match name - extract everything after "name:" (but not task names) if ($testLine.Contains('name:') -and -not $testLine.Contains('name: "') -and -not $testLine.Contains('- name:')) { $nameIndex = $testLine.IndexOf('name:') if ($nameIndex -ge 0) { $nameValue = $testLine.Substring($nameIndex + 5).Trim() $registryName = $nameValue } } # Match data - extract everything after "data:" if ($testLine.Contains('data:')) { $dataIndex = $testLine.IndexOf('data:') if ($dataIndex -ge 0) { $dataValue = $testLine.Substring($dataIndex + 5).Trim() $registryValue = $dataValue -replace '^["'']|["'']$', '' } } # Match type - extract everything after "type:" if ($testLine.Contains('type:')) { $typeIndex = $testLine.IndexOf('type:') if ($typeIndex -ge 0) { $typeValue = $testLine.Substring($typeIndex + 5).Trim() $registryType = $typeValue # We have all we need, process and break if ($registryPath -and $registryName -and $null -ne $registryValue -and $registryType) { # Handle HKU paths if ($registryPath -match '^HKU:\\\{\{\s*item\s*\}\}\\(.+)') { $userPath = $Matches[1] $registryPath = "HKCU:\$userPath" } elseif ($registryPath -match '^HKU:\\') { # Skip HKU without item variable break } # Handle variable references if ($registryValue -match '\{\{\s*([^}]+)\s*\}\}') { $varName = $Matches[1].Trim() $registryValue = Get-CISDefaultValue -VariableName $varName } $omaUri = Convert-RegistryPathToOMAURI -RegistryPath $registryPath -RegistryName $registryName $results += @{ ControlId = $controlId Type = "Registry" RegistryPath = $registryPath RegistryName = $registryName Value = $registryValue DataType = $registryType OMAUri = $omaUri Level1 = $hasLevel1 Level2 = $hasLevel2 SourceFile = $YAMLPath } } break # We have all we need } } } } } # Extract security policy settings (win_security_policy) - line-by-line approach $inSecurityPolicyBlock = $false $currentSection = $null $currentKey = $null $currentValue = $null for ($i = 0; $i -lt $lines.Count; $i++) { $line = $lines[$i] if ($line -match 'community\.windows\.win_security_policy:') { $inSecurityPolicyBlock = $true $currentSection = $null $currentKey = $null $currentValue = $null continue } if ($inSecurityPolicyBlock) { if ($line -match '^\s*-\s+name:') { if ($currentSection -and $currentKey -and $currentValue) { if ($currentValue -match '\{\{\s*([^}]+)\s*\}\}') { $varName = $Matches[1].Trim() $currentValue = Get-CISDefaultValue -VariableName $varName } $omaUri = Convert-SecurityPolicyToOMAURI -Section $currentSection -Key $currentKey $results += @{ ControlId = $controlId Type = "SecurityPolicy" Section = $currentSection Key = $currentKey Value = $currentValue OMAUri = $omaUri Level1 = $hasLevel1 Level2 = $hasLevel2 SourceFile = $YAMLPath } } $inSecurityPolicyBlock = $false continue } if ($line -match '^\s+section:\s*(.+)') { $currentSection = $Matches[1].Trim() } elseif ($line -match '^\s+key:\s*(.+)') { $currentKey = $Matches[1].Trim() } elseif ($line -match '^\s+value:\s*(.+)') { $currentValue = $Matches[1].Trim() $currentValue = $currentValue -replace '^["'']|["'']$', '' } elseif ($line -match '^\s+(when|tags):') { if ($currentSection -and $currentKey -and $currentValue) { if ($currentValue -match '\{\{\s*([^}]+)\s*\}\}') { $varName = $Matches[1].Trim() $currentValue = Get-CISDefaultValue -VariableName $varName } $omaUri = Convert-SecurityPolicyToOMAURI -Section $currentSection -Key $currentKey $results += @{ ControlId = $controlId Type = "SecurityPolicy" Section = $currentSection Key = $currentKey Value = $currentValue OMAUri = $omaUri Level1 = $hasLevel1 Level2 = $hasLevel2 SourceFile = $YAMLPath } } $inSecurityPolicyBlock = $false } } } # Handle last security policy block if ($inSecurityPolicyBlock -and $currentSection -and $currentKey -and $currentValue) { if ($currentValue -match '\{\{\s*([^}]+)\s*\}\}') { $varName = $Matches[1].Trim() $currentValue = Get-CISDefaultValue -VariableName $varName } $omaUri = Convert-SecurityPolicyToOMAURI -Section $currentSection -Key $currentKey $results += @{ ControlId = $controlId Type = "SecurityPolicy" Section = $currentSection Key = $currentKey Value = $currentValue OMAUri = $omaUri Level1 = $hasLevel1 Level2 = $hasLevel2 SourceFile = $YAMLPath } } # Extract user rights assignments (win_user_right) - improved regex $userRightPattern = '(?:ansible\.windows\.win_user_right|win_user_right):\s*\n\s*name:\s*([^\n]+)\s*\n\s*users:\s*\n((?:\s*-\s+[^\n]+\n?)+)' $userRightMatches = [regex]::Matches($content, $userRightPattern, [System.Text.RegularExpressions.RegexOptions]::Multiline) foreach ($match in $userRightMatches) { $rightName = $match.Groups[1].Value.Trim() $usersBlock = $match.Groups[2].Value $users = ($usersBlock -split '\n' | Where-Object { $_ -match '^\s*-\s+' } | ForEach-Object { $_ -replace '^\s*-\s+', '' }).Trim() # Convert user right to OMA-URI $omaUri = Convert-UserRightToOMAURI -RightName $rightName $results += @{ ControlId = $controlId Type = "UserRight" RightName = $rightName Users = $users OMAUri = $omaUri Level1 = $hasLevel1 Level2 = $hasLevel2 SourceFile = $YAMLPath } } # Extract service settings (win_service) $servicePattern = '(?:ansible\.windows\.win_service|win_service):\s*\n\s*name:\s*([^\n]+)\s*\n\s*start_mode:\s*([^\n]+)' $serviceMatches = [regex]::Matches($content, $servicePattern, [System.Text.RegularExpressions.RegexOptions]::Multiline) foreach ($match in $serviceMatches) { $serviceName = $match.Groups[1].Value.Trim() $startMode = $match.Groups[2].Value.Trim() # Convert service to registry setting (services are managed via registry) $registryPath = "HKLM:\SYSTEM\CurrentControlSet\Services\$serviceName" $registryName = "Start" $registryValue = switch ($startMode.ToLower()) { "disabled" { "4" } "manual" { "3" } "automatic" { "2" } default { "4" } } $omaUri = Convert-RegistryPathToOMAURI -RegistryPath $registryPath -RegistryName $registryName $results += @{ ControlId = $controlId Type = "Service" ServiceName = $serviceName StartMode = $startMode RegistryPath = $registryPath RegistryName = $registryName Value = $registryValue DataType = "dword" OMAUri = $omaUri Level1 = $hasLevel1 Level2 = $hasLevel2 SourceFile = $YAMLPath } } # Extract audit policy settings (win_shell with AuditPol commands) - Section 17 $auditPolPattern = 'AuditPol\s+/set\s+/subcategory:"([^"]+)"\s+/(success|failure):(enable|disable)' $auditPolMatches = [regex]::Matches($content, $auditPolPattern, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) foreach ($match in $auditPolMatches) { $subcategory = $match.Groups[1].Value.Trim() $auditType = $match.Groups[2].Value.Trim() $auditState = $match.Groups[3].Value.Trim() # Convert audit policy to registry setting (audit policies are stored in registry) # Audit policies are typically configured via Group Policy or Intune Scripts # For now, we'll note these as requiring scripts $results += @{ ControlId = $controlId Type = "AuditPolicy" Subcategory = $subcategory AuditType = $auditType State = $auditState RequiresScript = $true Level1 = $hasLevel1 Level2 = $hasLevel2 SourceFile = $YAMLPath } } return $results } function Convert-RegistryPathToOMAURI { param( [string]$RegistryPath, [string]$RegistryName ) # Convert HKLM:\Software\Policies\Microsoft\... to OMA-URI if ($RegistryPath -match '^HKLM:\\Software\\Policies\\(.+)') { $policyPath = $Matches[1] -replace '\\', '/' -replace ' ', '' return "./Device/Vendor/MSFT/Policy/Config/$policyPath~$RegistryName" } # Convert HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\... to OMA-URI if ($RegistryPath -match '^HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\(.+)') { $policyPath = $Matches[1] -replace '\\', '/' -replace ' ', '' return "./Device/Vendor/MSFT/Policy/Config/$policyPath~$RegistryName" } # Convert HKCU:\... to OMA-URI (user-level policies) if ($RegistryPath -match '^HKCU:\\(.+)') { $userPath = $Matches[1] -replace '\\', '/' -replace ' ', '' return "./User/Vendor/MSFT/Policy/Config/$userPath~$RegistryName" } # Convert HKLM:\SYSTEM\CurrentControlSet\Services\... to OMA-URI (for services) if ($RegistryPath -match '^HKLM:\\SYSTEM\\CurrentControlSet\\Services\\([^\\]+)\\Start$') { $serviceName = $Matches[1] return "./Device/Vendor/MSFT/Policy/Config/Services~$serviceName~Start" } # Convert HKLM:\SYSTEM\CurrentControlSet\Control\... to OMA-URI if ($RegistryPath -match '^HKLM:\\SYSTEM\\CurrentControlSet\\Control\\(.+)') { $controlPath = $Matches[1] -replace '\\', '/' -replace ' ', '' return "./Device/Vendor/MSFT/Policy/Config/System~$controlPath~$RegistryName" } # Convert HKLM:\SYSTEM\CurrentControlSet\Services\... to OMA-URI if ($RegistryPath -match '^HKLM:\\SYSTEM\\CurrentControlSet\\Services\\(.+)') { $servicePath = $Matches[1] -replace '\\', '/' -replace ' ', '' return "./Device/Vendor/MSFT/Policy/Config/Services~$servicePath~$RegistryName" } # Fallback: use custom configuration return "./Device/Vendor/MSFT/CustomPolicy/$RegistryName" } function Get-CISDefaultValue { param([string]$VariableName) # Common CIS default values from defaults/main.yml $defaults = @{ "win11cis_max_passwords_saved" = "24" "win11cis_maximum_password_age" = "365" "win11cis_minimum_password_age" = "1" "win11cis_minimum_password_length" = "14" "win11cis_account_lockout_threshold" = "5" "win11cis_account_lockout_duration" = "15" "win11cis_reset_account_lockout_counter_after" = "15" } if ($defaults.ContainsKey($VariableName)) { return $defaults[$VariableName] } else { return "0" } } function Convert-SecurityPolicyToOMAURI { param( [string]$Section, [string]$Key ) # Map security policy sections to OMA-URI $sectionMap = @{ "System Access" = "SystemAccess" "Kerberos Policy" = "Kerberos" } $mappedSection = if ($sectionMap.ContainsKey($Section)) { $sectionMap[$Section] } else { $Section } return "./Device/Vendor/MSFT/Policy/Config/$mappedSection~$Key" } function Convert-UserRightToOMAURI { param( [string]$RightName ) # Map user rights to OMA-URI $rightMap = @{ "SeNetworkLogonRight" = "UserRights/AccessThisComputerFromNetwork" "SeInteractiveLogonRight" = "UserRights/AllowLogOnLocally" "SeRemoteInteractiveLogonRight" = "UserRights/AllowLogOnThroughRemoteDesktopServices" "SeBackupPrivilege" = "UserRights/BackupFilesAndDirectories" "SeRestorePrivilege" = "UserRights/RestoreFilesAndDirectories" "SeSystemTimePrivilege" = "UserRights/ChangeSystemTime" "SeCreateTokenPrivilege" = "UserRights/CreateToken" "SeDebugPrivilege" = "UserRights/DebugPrograms" "SeDenyNetworkLogonRight" = "UserRights/DenyAccessFromNetwork" "SeDenyInteractiveLogonRight" = "UserRights/DenyLogOnLocally" "SeDenyRemoteInteractiveLogonRight" = "UserRights/DenyLogOnThroughRemoteDesktopServices" "SeEnableDelegationPrivilege" = "UserRights/EnableDelegation" "SeRemoteShutdownPrivilege" = "UserRights/ForceRemoteShutdown" "SeAuditPrivilege" = "UserRights/GenerateSecurityAudits" "SeImpersonatePrivilege" = "UserRights/ImpersonateClient" "SeIncreaseWorkingSetPrivilege" = "UserRights/IncreaseWorkingSet" "SeIncreaseBasePriorityPrivilege" = "UserRights/IncreaseSchedulingPriority" "SeLoadDriverPrivilege" = "UserRights/LoadUnloadDeviceDrivers" "SeLockMemoryPrivilege" = "UserRights/LockPagesInMemory" "SeMachineAccountPrivilege" = "UserRights/AddWorkstationsToDomain" "SeManageVolumePrivilege" = "UserRights/ManageVolume" "SeProfileSingleProcessPrivilege" = "UserRights/ProfileSingleProcess" "SeRelabelPrivilege" = "UserRights/ModifyObjectLabels" "SeShutdownPrivilege" = "UserRights/ShutDownSystem" "SeSyncAgentPrivilege" = "UserRights/SynchronizeDirectoryServiceData" "SeTakeOwnershipPrivilege" = "UserRights/TakeOwnership" "SeTcbPrivilege" = "UserRights/ActAsPartOfOperatingSystem" "SeTrustedCredManAccessPrivilege" = "UserRights/AccessCredentialManagerAsTrustedCaller" "SeUndockPrivilege" = "UserRights/RemoveComputerFromDockingStation" "SeCreateGlobalPrivilege" = "UserRights/CreateGlobalObjects" "SeCreatePermanentPrivilege" = "UserRights/CreatePermanentSharedObjects" "SeCreateSymbolicLinkPrivilege" = "UserRights/CreateSymbolicLinks" "SeIncreaseQuotaPrivilege" = "UserRights/AdjustMemoryQuotasForProcess" } $mappedRight = if ($rightMap.ContainsKey($RightName)) { $rightMap[$RightName] } else { "UserRights/$RightName" } return "./Device/Vendor/MSFT/Policy/Config/$mappedRight" } |