Public/Test-EnvironmentProtection.ps1
|
function Test-EnvironmentProtection { [CmdletBinding()] [OutputType([PSCustomObject[]])] param( [Parameter(Mandatory)] [ValidatePattern('^[a-zA-Z0-9._-]+$')] [string]$Owner, [Parameter(Mandatory)] [ValidatePattern('^[a-zA-Z0-9._-]+$')] [string]$Repo, [Parameter(Mandatory)] [string]$Token ) $target = "$Owner/$Repo" $results = [System.Collections.Generic.List[PSCustomObject]]::new() try { $response = Invoke-GitHubApi -Endpoint "repos/$Owner/$Repo/environments" -Token $Token } catch { $msg = $_.Exception.Message if ($msg -match '404') { $results.Add((Format-FylgyrResult ` -CheckName 'EnvironmentProtection' ` -Status 'Pass' ` -Severity 'Info' ` -Resource $target ` -Detail 'No deployment environments defined on this repository.' ` -Remediation 'No action needed. If you later add deployment environments, configure required reviewers and wait timers.' ` -Target $target)) return $results.ToArray() } if ($msg -match '403') { $results.Add((Format-FylgyrResult ` -CheckName 'EnvironmentProtection' ` -Status 'Error' ` -Severity 'Medium' ` -Resource $target ` -Detail 'Insufficient permissions to list deployment environments.' ` -Remediation 'Use a fine-grained token with Environments:read permission, or a classic token with repo scope.' ` -Target $target)) return $results.ToArray() } $results.Add((Format-FylgyrResult ` -CheckName 'EnvironmentProtection' ` -Status 'Error' ` -Severity 'Medium' ` -Resource $target ` -Detail "Unexpected error reading environments: $($_.Exception.Message)" ` -Remediation 'Re-run with a valid token and verify network access to api.github.com.' ` -Target $target)) return $results.ToArray() } $environments = @() if ($response -and $response.PSObject.Properties['environments'] -and $response.environments) { $environments = @($response.environments) } if ($environments.Count -eq 0) { $results.Add((Format-FylgyrResult ` -CheckName 'EnvironmentProtection' ` -Status 'Pass' ` -Severity 'Info' ` -Resource $target ` -Detail 'No deployment environments defined on this repository.' ` -Remediation 'No action needed. If you later add deployment environments, configure required reviewers and wait timers.' ` -Target $target)) return $results.ToArray() } $findings = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($env in $environments) { $envName = $env.name $envResource = "$target (environment: $envName)" $hasRequiredReviewers = $false $hasWaitTimer = $false $hasBranchPolicy = $false if ($env.protection_rules) { foreach ($rule in $env.protection_rules) { if ($rule.type -eq 'required_reviewers' -and $rule.reviewers -and $rule.reviewers.Count -gt 0) { $hasRequiredReviewers = $true } if ($rule.type -eq 'wait_timer' -and $rule.wait_timer -gt 0) { $hasWaitTimer = $true } } } if ($env.PSObject.Properties['deployment_branch_policy'] -and $env.deployment_branch_policy) { $hasBranchPolicy = $true } if (-not $hasRequiredReviewers) { $severity = 'High' $detail = "Environment '$envName' has no required reviewers. Deployments can proceed without human approval, turning a compromised PR or workflow into a direct path to this environment." if (-not $hasWaitTimer) { $detail += ' No wait timer is configured either, so there is no delay window for manual intervention.' } if (-not $hasBranchPolicy) { $detail += ' No deployment branch policy is set - any ref can deploy to this environment.' } $findings.Add((Format-FylgyrResult ` -CheckName 'EnvironmentProtection' ` -Status 'Fail' ` -Severity $severity ` -Resource $envResource ` -Detail $detail ` -Remediation "Add at least one required reviewer to the '$envName' environment in Settings > Environments. For production, also configure a wait timer and a deployment branch policy restricting which refs can deploy." ` -AttackMapping @('unauthorized-env-deployment', 'prt-scan-ai-automated') ` -Target $target)) } elseif (-not $hasBranchPolicy) { $findings.Add((Format-FylgyrResult ` -CheckName 'EnvironmentProtection' ` -Status 'Warning' ` -Severity 'Medium' ` -Resource $envResource ` -Detail "Environment '$envName' has required reviewers but no deployment branch policy. Any branch can trigger a deployment that (if approved) reaches this environment." ` -Remediation "Add a deployment branch policy in Settings > Environments to restrict deployments to protected or specific branches." ` -AttackMapping @('unauthorized-env-deployment') ` -Target $target)) } } if ($findings.Count -eq 0) { $results.Add((Format-FylgyrResult ` -CheckName 'EnvironmentProtection' ` -Status 'Pass' ` -Severity 'Info' ` -Resource $target ` -Detail "$($environments.Count) environment(s) checked, all have required reviewers and deployment branch policies." ` -Remediation 'No action needed.' ` -Target $target)) } else { foreach ($f in $findings) { $results.Add($f) } } $results.ToArray() } |