Public/Sync-ProwlerCheck.ps1
|
function Sync-ProwlerCheck { <# .SYNOPSIS Syncs new Prowler checks from the upstream GitHub repository. .DESCRIPTION Downloads the Prowler repository as a zip archive (single HTTP request) and walks the extracted filesystem to discover checks. Only checks not already present locally are copied. New checks are automatically converted to PowerShell format. This replaces per-file downloads (~1,500 HTTP requests) with a single archive download + local filesystem walk. .PARAMETER Provider Filter to specific provider(s) (azure, aws, gcp). Accepts one or more values. If not specified, syncs all providers defined in CIEM config. .PARAMETER Service Filter to specific service(s) (e.g., entra, iam, storage). Accepts one or more values. .PARAMETER Ref Branch, tag, or commit SHA to sync from. Defaults to 'master'. .EXAMPLE Sync-ProwlerCheck # Syncs all check files for supported providers .EXAMPLE Sync-ProwlerCheck -Provider azure -Service entra # Syncs only Entra-related checks .EXAMPLE Sync-ProwlerCheck -Provider azure, aws -Verbose # Syncs checks for both Azure and AWS providers .EXAMPLE Sync-ProwlerCheck -Ref 'v4.0.0' # Syncs checks from the v4.0.0 tag #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter()] [ValidateSet('azure', 'aws', 'gcp')] [string[]]$Provider, [Parameter()] [string[]]$Service, [Parameter()] [string]$Ref = 'master' ) $ErrorActionPreference = 'Stop' # Resolve local prowler providers path $prowlerProvidersPath = Join-Path $script:ModuleRoot $script:Config.prowler.path $providersToSync = if ($Provider) { @($Provider) } else { @((Get-CIEMProvider).Name.ToLower()) } Write-Verbose "Syncing Prowler checks from GitHub..." Write-Verbose " Providers: $($providersToSync -join ', ')" if ($Service) { Write-Verbose " Services: $($Service -join ', ')" } Write-Verbose " Ref: $Ref" Write-Verbose " Local path: $prowlerProvidersPath" # Download and extract the repository archive (single HTTP request) Write-Verbose "Downloading Prowler repository archive..." $archive = Save-GitHubRepoArchive -Owner 'prowler-cloud' -Repo 'prowler' -Ref $Ref -ErrorAction Stop $success = [System.Collections.Generic.List[string]]::new() $failed = [System.Collections.Generic.List[string]]::new() $skipped = [System.Collections.Generic.List[string]]::new() try { # Walk the extracted archive to discover checks # Archive structure: {prefix}/prowler/providers/{provider}/services/{service}/{checkName}/ $archiveProvidersPath = Join-Path $archive.ExtractedPath 'prowler/providers' if (-not (Test-Path $archiveProvidersPath)) { Write-Verbose "No prowler/providers directory found in archive." return [PSCustomObject]@{ Success = @(); Failed = @(); Skipped = @() } } foreach ($providerName in $providersToSync) { $archiveProviderPath = Join-Path $archiveProvidersPath $providerName if (-not (Test-Path $archiveProviderPath)) { Write-Verbose " Provider '$providerName' not found in archive, skipping." continue } $archiveServicesPath = Join-Path $archiveProviderPath 'services' if (-not (Test-Path $archiveServicesPath)) { Write-Verbose " No services directory for '$providerName', skipping." continue } # Get service directories, optionally filtered $serviceDirs = Get-ChildItem -Path $archiveServicesPath -Directory if ($Service) { $serviceDirs = $serviceDirs | Where-Object { $_.Name -in $Service } } foreach ($serviceDir in $serviceDirs) { # Each subdirectory under the service that contains a {name}.metadata.json is a check $checkDirs = Get-ChildItem -Path $serviceDir.FullName -Directory foreach ($checkDir in $checkDirs) { $checkName = $checkDir.Name $metadataFile = Join-Path $checkDir.FullName "$checkName.metadata.json" if (-not (Test-Path $metadataFile)) { continue } # Check if already exists locally $localCheckDir = Join-Path $prowlerProvidersPath "$providerName/services/$($serviceDir.Name)/$checkName" if (Test-Path $localCheckDir) { Write-Verbose " Skipping $checkName - already exists locally" $skipped.Add($checkName) continue } Write-Verbose " Copying $checkName..." try { # Ensure parent directory exists $localParent = Split-Path $localCheckDir -Parent if (-not (Test-Path $localParent)) { New-Item -ItemType Directory -Path $localParent -Force | Out-Null } Copy-Item -Path $checkDir.FullName -Destination $localCheckDir -Recurse -Force -ErrorAction Stop Write-Verbose " Copied" $success.Add($checkName) } catch { Write-Verbose " Failed: $_" $failed.Add($checkName) } } } } # Convert newly downloaded checks to PowerShell if ($success.Count -gt 0) { Write-Verbose "Converting $($success.Count) new check(s) to PowerShell..." foreach ($checkName in $success) { # Find the check directory we just copied $localCheckDir = Get-ChildItem -Path $prowlerProvidersPath -Recurse -Directory | Where-Object { $_.Name -eq $checkName } | Select-Object -First 1 -ExpandProperty FullName if ($localCheckDir) { Write-Verbose " Converting: $checkName" try { Convert-ProwlerCheck -CheckPath $localCheckDir | Out-Null Write-Verbose " Done" } catch { Write-Verbose " Conversion failed: $_" } } } } Write-Verbose "Summary: Downloaded=$($success.Count), Skipped=$($skipped.Count), Failed=$($failed.Count)" [PSCustomObject]@{ Success = @($success) Failed = @($failed) Skipped = @($skipped) } } finally { # Clean up the temp directory if ($archive.TempDirectory -and (Test-Path $archive.TempDirectory)) { Write-Verbose "Cleaning up temp directory..." Remove-Item $archive.TempDirectory -Recurse -Force -ErrorAction SilentlyContinue } } } |