modules/Invoke-IaCTerraform.ps1
|
#Requires -Version 7.0 <# .SYNOPSIS Wrapper for Terraform IaC validation. .DESCRIPTION Runs terraform validate (syntax-only, no init required for basic validation) and trivy config (which subsumes tfsec) for HCL security scanning. Returns findings as PSObjects in the standard v1 wrapper envelope. Design decision: uses trivy config instead of standalone tfsec because Aqua merged tfsec into trivy. Since trivy is already in the azure-analyzer tool manifest, this avoids adding another external dependency and keeps the tool surface smaller. Never throws -- designed for graceful degradation in the orchestrator. Security: All output passes through Remove-Credentials. Clones go through RemoteClone.ps1 (HTTPS-only, host allow-list). .PARAMETER Repository Path to the repository root containing .tf files. Defaults to '.'. Aliases: Repo, RepoPath, Path '.'. .PARAMETER RemoteUrl Remote repository URL to clone and scan. #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] param ( [Alias('Repo', 'RepoPath', 'Path')] [string] $Repository = '.', [string] $RemoteUrl ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' # Dot-source shared modules $sharedDir = Join-Path (Split-Path $PSScriptRoot -Parent) 'modules' 'shared' if (-not $sharedDir -or -not (Test-Path $sharedDir)) { $sharedDir = Join-Path $PSScriptRoot 'shared' } $sanitizePath = Join-Path $sharedDir 'Sanitize.ps1' if (Test-Path $sanitizePath) { . $sanitizePath } $retryPath = Join-Path $sharedDir 'Retry.ps1' if (Test-Path $retryPath) { . $retryPath } $remoteClonePath = Join-Path $sharedDir 'RemoteClone.ps1' if (Test-Path $remoteClonePath) { . $remoteClonePath } $missingToolPath = Join-Path $sharedDir 'MissingTool.ps1' if (Test-Path $missingToolPath) { . $missingToolPath } if (-not (Get-Command Write-MissingToolNotice -ErrorAction SilentlyContinue)) { function Write-MissingToolNotice { param([string]$Tool, [string]$Message) Write-Warning $Message } } $adapterPath = Join-Path $PSScriptRoot 'iac' 'IaCAdapters.ps1' if (Test-Path $adapterPath) { . $adapterPath } $envelopePath = Join-Path $sharedDir 'New-WrapperEnvelope.ps1' if (Test-Path $envelopePath) { . $envelopePath } if (-not (Get-Command New-WrapperEnvelope -ErrorAction SilentlyContinue)) { function New-WrapperEnvelope { param([string]$Source,[string]$Status='Failed',[string]$Message='',[object[]]$FindingErrors=@()) return [PSCustomObject]@{ Source=$Source; SchemaVersion='1.0'; Status=$Status; Message=$Message; Findings=@(); Errors=@($FindingErrors) } } } if (-not (Get-Command Remove-Credentials -ErrorAction SilentlyContinue)) { function Remove-Credentials { param ([string]$Text) return $Text } } # At least one of terraform or trivy must be available $hasTerraform = $null -ne (Get-Command terraform -ErrorAction SilentlyContinue) $hasTrivy = $null -ne (Get-Command trivy -ErrorAction SilentlyContinue) if (-not $hasTerraform -and -not $hasTrivy) { Write-MissingToolNotice -Tool 'terraform-iac' -Message "Neither terraform nor trivy CLI is installed. Skipping Terraform IaC validation." return [PSCustomObject]@{ Source = 'terraform-iac' SchemaVersion = '1.0' Status = 'Skipped' Message = 'Neither terraform nor trivy CLI installed. Install terraform from https://developer.hashicorp.com/terraform/install or trivy from https://github.com/aquasecurity/trivy/releases' Findings = @() Errors = @() } } $cloneInfo = $null $cleanupClone = $null try { if ($RemoteUrl) { if (-not (Get-Command Invoke-RemoteRepoClone -ErrorAction SilentlyContinue)) { Write-Warning "RemoteClone helper not loaded; cannot scan remote URL." return [PSCustomObject]@{ Source = 'terraform-iac' SchemaVersion = '1.0'; Status = 'Failed' Message = 'RemoteClone helper unavailable'; Findings = @() Errors = @() } } $cloneInfo = Invoke-RemoteRepoClone -RepoUrl $RemoteUrl if (-not $cloneInfo) { return [PSCustomObject]@{ Source = 'terraform-iac' SchemaVersion = '1.0'; Status = 'Failed' Message = "Remote clone failed or host not on allow-list: $RemoteUrl" Findings = @() Errors = @() } } $cleanupClone = $cloneInfo.Cleanup $Repository = $cloneInfo.Path } if (-not (Test-Path $Repository)) { return [PSCustomObject]@{ Source = 'terraform-iac' SchemaVersion = '1.0'; Status = 'Failed' Message = "Repository path not found: $Repository"; Findings = @() Errors = @() } } Write-Verbose "Running Terraform IaCvalidation on '$Repository'" if (-not (Get-Command Invoke-IaCAdapter -ErrorAction SilentlyContinue)) { Write-Warning "IaCAdapters module not loaded. Terraform IaC validation cannot proceed." return [PSCustomObject]@{ Source = 'terraform-iac' SchemaVersion = '1.0'; Status = 'Failed' Message = 'IaCAdapters module not loaded. Ensure modules/iac/IaCAdapters.ps1 is present.' Findings = @() Errors = @() } } return Invoke-IaCAdapter -Flavour 'terraform' -RepoPath $Repository -SourceRepoUrl $RemoteUrl } catch { Write-Warning "Terraform IaC validation failed: $(Remove-Credentials -Text ([string]$_))" return [PSCustomObject]@{ Source = 'terraform-iac' SchemaVersion = '1.0' Status = 'Failed' Message = Remove-Credentials -Text ([string]$_) Findings = @() Errors = @() } } finally { if ($cleanupClone) { try { & $cleanupClone } catch { Write-Verbose "Terraform clone cleanup failed: $(Remove-Credentials -Text ([string]$_.Exception.Message))" } } } |