Public/Test-IpAllowlist.ps1
|
function Test-IpAllowlist { [CmdletBinding()] [OutputType([PSCustomObject[]])] param( [Parameter(Mandatory)] [ValidatePattern('^[a-zA-Z0-9._-]+$')] [string]$Owner, [Parameter(Mandatory)] [string]$Token ) $resource = "org/$Owner" $results = [System.Collections.Generic.List[PSCustomObject]]::new() $ownerContext = Get-FylgyrOwnerContext -Owner $Owner -Token $Token if ($ownerContext.Type -eq 'User') { $results.Add((Format-FylgyrResult ` -CheckName 'IpAllowlist' ` -Status 'Info' ` -Severity 'Info' ` -Resource $resource ` -Detail "Owner '$Owner' is a personal account. Organization IP allowlist does not apply." ` -Remediation 'No action needed. Run this check against an organization owner.' ` -Target $resource)) return $results.ToArray() } $query = @' query($org: String!) { organization(login: $org) { ipAllowListEntries(first: 1) { totalCount } } } '@ try { $response = Invoke-GitHubApi -GraphQL -Query $query -Variables @{ org = $Owner } -Token $Token } catch { $msg = $_.Exception.Message if ($msg -match 'Enterprise' -or $msg -match 'enterprise' -or $msg -match 'not available') { $results.Add((Format-FylgyrResult ` -CheckName 'IpAllowlist' ` -Status 'Info' ` -Severity 'Info' ` -Resource $resource ` -Detail 'IP allowlist is not available for this organization plan. This feature requires GitHub Enterprise Cloud.' ` -Remediation 'Use identity-aware controls first: enforce SSO, require MFA, protect rulesets, and prefer short-lived GitHub App tokens.' ` -Target $resource)) return $results.ToArray() } if ($msg -match '403' -or $msg -match 'FORBIDDEN' -or $msg -match 'Resource not accessible by personal access token' -or $msg -match 'right permission to retrieve') { $results.Add((Format-FylgyrResult ` -CheckName 'IpAllowlist' ` -Status 'Info' ` -Severity 'Info' ` -Resource $resource ` -Detail 'Unable to verify organization IP allowlist via GraphQL with the current token/plan. This check is advisory and may be unavailable depending on enterprise identity/IP policy mode.' ` -Remediation 'Treat this as a recommendation signal. If you want verification, use a token with organization Administration:read and confirm enterprise IP allowlist mode permits GraphQL access.' ` -Target $resource)) return $results.ToArray() } $results.Add((Format-FylgyrResult ` -CheckName 'IpAllowlist' ` -Status 'Info' ` -Severity 'Info' ` -Resource $resource ` -Detail "Unable to verify IP allowlist currently: $($_.Exception.Message)" ` -Remediation 'Treat this check as advisory when GraphQL validation is unavailable. Use enterprise/org documentation and UI settings for manual confirmation.' ` -Target $resource)) return $results.ToArray() } $entryCount = 0 if ($response -and $response.PSObject.Properties['data'] -and $response.data -and $response.data.PSObject.Properties['organization'] -and $response.data.organization -and $response.data.organization.PSObject.Properties['ipAllowListEntries']) { $entryCount = [int]$response.data.organization.ipAllowListEntries.totalCount } if ($entryCount -gt 0) { $results.Add((Format-FylgyrResult ` -CheckName 'IpAllowlist' ` -Status 'Pass' ` -Severity 'Info' ` -Resource $resource ` -Detail "Organization has $entryCount IP allowlist entr$(if ($entryCount -eq 1) { 'y' } else { 'ies' })." ` -Remediation 'No action needed. Keep the allowlist reviewed and current.' ` -Target $resource)) } else { $results.Add((Format-FylgyrResult ` -CheckName 'IpAllowlist' ` -Status 'Warning' ` -Severity 'Medium' ` -Resource $resource ` -Detail 'Organization has no IP allowlist entries. This is an advisory defense-in-depth recommendation for stolen-token scenarios.' ` -Remediation 'If on Enterprise Cloud, consider configuring IP allowlist as a supplemental control. Pair with SSO/device trust, rulesets, and short-lived GitHub App installation tokens.' ` -AttackMapping @('github-device-code-phishing', 'uber-credential-leak') ` -Target $resource)) } $results.ToArray() } |