diff --git a/README.md b/README.md index ee6f35a..f321174 100644 --- a/README.md +++ b/README.md @@ -95,16 +95,19 @@ Once the PAT is created, GitHub will present it to you as shown below. You must 2. Load `github.ps1` in your current PowerShell session: ```powershell - . ./github.ps1 + . ./githound.ps1 ``` -3. Create a GitHub Session using your Personal Access Token. +3. Create a GitHub Session using your Personal Access Token (PAT) or through a Github App. ```powershell +#Github Session with PAT $session = New-GitHubSession -OrganizationName -Token (Get-Clipboard) +#Github Session with Github App, require powershell 7.0 or later +$session = New-GithubAppSession -OrganizationName -ClientId (Get-Clipboard) -PrivateKeyPath ``` -Note: You must specify the name of your GitHub organziation. For example, this repository is part of the `SpecterOps` organization, so I would specify `SpecterOps` as the argument for the OrganizationName parameter. Additionally, you must specify your Personal Access Token. I find that it is easiest to paste it directly from the clipboard as this is where it will be after you create it or if you save it in a password manager. +Note: You must specify the name of your GitHub organziation. For example, this repository is part of the `SpecterOps` organization, so I would specify `SpecterOps` as the argument for the OrganizationName parameter. Additionally, you must specify your Personal Access Token (or Priv key and Client ID). I find that it is easiest to paste it directly from the clipboard as this is where it will be after you create it or if you save it in a password manager. 4. Run the collection on the specified organization: diff --git a/githound.ps1 b/githound.ps1 index 2ac6ffb..1d258ce 100644 --- a/githound.ps1 +++ b/githound.ps1 @@ -59,6 +59,55 @@ function New-GithubSession { OrganizationName = $OrganizationName } } +function New-GithubAppSession { + [OutputType('GitHound.Session')] + [CmdletBinding()] + Param( + [Parameter(Position=0, Mandatory = $true)] + [string] + $OrganizationName, + + [Parameter(Position=1, Mandatory = $true)] + [string] + $ClientId, + + [Parameter(Position=2, Mandatory = $false)] + [string] + $PrivateKeyPath = './priv.pem' + ) + + $header = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes((ConvertTo-Json -InputObject @{ + alg = "RS256" + typ = "JWT" + }))).TrimEnd('=').Replace('+', '-').Replace('/', '_'); + + $payload = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes((ConvertTo-Json -InputObject @{ + iat = [System.DateTimeOffset]::UtcNow.AddSeconds(-30).ToUnixTimeSeconds() + exp = [System.DateTimeOffset]::UtcNow.AddMinutes(5).ToUnixTimeSeconds() + iss = $ClientId + }))).TrimEnd('=').Replace('+', '-').Replace('/', '_'); + + $rsa = [System.Security.Cryptography.RSA]::Create() + $rsa.ImportFromPem((Get-Content $PrivateKeyPath -Raw)) + + $signature = [Convert]::ToBase64String($rsa.SignData([System.Text.Encoding]::UTF8.GetBytes("$header.$payload"), [System.Security.Cryptography.HashAlgorithmName]::SHA256, [System.Security.Cryptography.RSASignaturePadding]::Pkcs1)).TrimEnd('=').Replace('+', '-').Replace('/', '_') + $jwt = "$header.$payload.$signature" + + $presession = New-GithubSession -OrganizationName $OrganizationName -Token $jwt + + $Installation = Invoke-GithubRestMethod -Session $presession -Path "app/installations" + + if ($null -eq $Installation -or $Installation.Count -eq 0) { + throw "No installations found for the GitHub App in the organization '$OrganizationName'." + } elseif ($Installation.Count -gt 1) { + throw "Multiple installations found for the GitHub App in the organization '$OrganizationName'. Please specify a single installation." + } + + $AccessToken = Invoke-GithubRestMethod -Session $presession -Path "app/installations/$($Installation.id)/access_tokens" -Method 'POST' + + New-GithubSession -OrganizationName $OrganizationName -Token $AccessToken.token + +} function Invoke-GithubRestMethod { [CmdletBinding()] @@ -80,10 +129,10 @@ function Invoke-GithubRestMethod { try { do { if($LinkHeader) { - $Response = Invoke-WebRequest -Uri "$LinkHeader" -Headers $Session.Headers -Method Get -ErrorAction Stop + $Response = Invoke-WebRequest -Uri "$LinkHeader" -Headers $Session.Headers -Method $Method -ErrorAction Stop } else { Write-Verbose "https://api.github.com/$($Path)" - $Response = Invoke-WebRequest -Uri "$($Session.Uri)$($Path)" -Headers $Session.Headers -Method Get -ErrorAction Stop + $Response = Invoke-WebRequest -Uri "$($Session.Uri)$($Path)" -Headers $Session.Headers -Method $Method -ErrorAction Stop } $Response.Content | ConvertFrom-Json | ForEach-Object { $_ }